orangejuice's blog

挂了请务必叫我

CF1519E - Off by One

题目大意

给定 \(n\) 个点 \((x_i,y_i)=(\frac{a_i}{b_i},\frac{c_i}{d_i})\) ,求一个最大的匹配

满足匹配的点对 \((x_i,y_i),(x_j,y_j)\) 每个点经过如下操作

\((x,y)\rightarrow (x+1,y) or (x,y+1)\)

之后可能满足 \(\frac{y_i'}{x_i'}=\frac{y'_j}{x_j'}\)

模型简化

按照 \(\frac{y'}{x'}\) 对于每个点经过两种可能变换的值分类,建立节点

我们需要决策每个 \(P_i\) 选的变换种类

显然每个斜率对应的个数为偶数时,都可以完成匹配

对于每一个 \(P_i\) ,设其两种变换之后变成的斜率对应节点为 \((u,v)\) ,那么连一条无向边

现在问题转化为对于无向边定向,使得最少的点入度为奇数

对于任意一个连通块,若其包含奇数条边,那么至少有一个点入度为奇数

否则一定可以完成匹配

具体的,随便选择一个点作为根,然后只对于祖先向子孙的边考虑关系

从子孙向上考虑所有的边,优先让子孙的入度为偶数

那么只有包含奇数条边时,根的入度为奇数,其他节点入度永远是偶数

即达到最优解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55

const int N=4e5+10,P=998244353;

int n,m;
struct Edge{
int to,nxt;
} e[N*2];
int head[N];

struct Node{
ll a,b;
bool operator < (const Node __) const { return a<__.a || (a==__.a && b<__.b); }
};
vector <int> G[N];

map <Node,int> M;
ll gcd(ll a,ll b){ return b==0?a:gcd(b,a%b); }
int Div(int a,int b,int c,int d){
ll x=1ll*a*d,y=1ll*b*c,g=gcd(x,y);
x/=g,y/=g;
Node t=(Node){x,y};
int &u=M[t];
if(!u) u=++m;
return u;
}

int vis[N],s[N],dfn;
void dfs(int u){
vis[u]=++dfn,s[u]=0;
for(int i=head[u];i;i=e[i].nxt) {
int v=e[i].to;
if(vis[v] && vis[v]<vis[u]) continue;
if(!vis[v]) dfs(v);
if(!s[v]) s[u]^=1,G[u].pb(i/2);
else s[v]^=1,G[v].pb(i/2);
}
}

int main(){
n=rd();
rep(i,1,n) {
int a=rd(),b=rd(),c=rd(),d=rd();
int u=Div(a+b,b,c,d),v=Div(a,b,c+d,d);
e[i*2]=(Edge){u,head[v]},head[v]=i*2;
e[i*2+1]=(Edge){v,head[u]},head[u]=i*2+1;
}
rep(i,1,m) if(!vis[i]) dfs(i);
int ans=0;
rep(i,1,m) ans+=G[i].size()/2;
printf("%d\n",ans);
rep(i,1,m) {
rep(j,0,G[i].size()/2-1)
printf("%d %d\n",G[i][j*2],G[i][j*2+1]);
}
}

CF1519F - Chests and Keys

题目大意

给定 \(n\) 个宝箱, \(m\) 种锁和对应的钥匙

每个箱子有 \(a_i\) 块钱,每种钥匙 \(b_i\) 块,给箱子 \(i\) 装上 \(j\) 这种锁需要 \(c_{i,j}\) 的代价

一个箱子可以装多把锁

求最小的代价,使得无论怎么买钥匙取开箱子都无法赚钱



\(n,m\leq 6,a_i,b_i\leq 4\)

??????

《关于Codeforce 3200 是朴素搜索一事》

复杂度上限?大概 \(2^{41}++\)

-> 873ms

再把-1判掉

->31ms

CodeForces Submission

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#include<bits/stdc++.h>
using namespace std;
#define rep(i,a,b) for(int i=a,i##end=b;i<=i##end;++i)
#define drep(i,a,b) for(int i=a,i##end=b;i>=i##end;--i)

const int N=10,P=998244353;

int n,m;
int a[N],b[N],c[N][N];
int A[1<<6],B[1<<6],s[N][1<<6],bin[1<<6];
int C[1<<6];

int st[N];
int ans=1e9;
void dfs(int p,int s){
if(s>=ans) return;
if(p==n) { ans=s; return; }
rep(S,0,(1<<m)-1) {
int fl=1;
if(s+::s[p][S]>=ans) continue;
st[p]=S;
drep(T,(1<<(p+1))-1,1<<p) {
C[T]=C[T^(1<<p)]|st[p];
if(A[T]>B[C[T]]) {
fl=0;
break;
}
}
if(fl) dfs(p+1,s+::s[p][S]);
}
}

int main(){
scanf("%d%d",&n,&m);
rep(i,0,max(n,m)-1) bin[1<<i]=i;
rep(i,0,n-1) scanf("%d",a+i);
rep(i,0,m-1) scanf("%d",b+i);
rep(i,0,n-1) rep(j,0,m-1) scanf("%d",c[i]+j);
rep(i,0,n-1) rep(j,i+1,n-1) if(a[j]>a[i]) swap(a[i],a[j]),swap(c[i],c[j]);
rep(S,1,(1<<n)-1) A[S]=A[S&(S-1)]+a[bin[S&-S]];
rep(S,1,(1<<m)-1) B[S]=B[S&(S-1)]+b[bin[S&-S]];
rep(i,0,n-1) rep(S,1,(1<<m)-1) s[i][S]=s[i][S&(S-1)]+c[i][bin[S&-S]];
int s=0;
rep(i,0,n-1) s+=a[i];
rep(i,0,m-1) s-=b[i];
if(s>0) return puts("-1"),0;
dfs(0,0);
printf("%d\n",ans==1e9?-1:ans);
}

CF1516E - Baby Ehab Plays with Permutations

题目大意

给定一个排列 \(1-n\) ,对于每个 \(i\in[1,k]\) ,求出恰好操作 \(i\) 能够生成的不同排列个数

分析

设排列为 \(P_i\) ,考虑对于最终态每个 \((i,P_i)\) 构成的环组进行 \(dp\)

一个长度为 \(n\) 的环有 \((n-1)!\) 种可能的排列,且需要至少 \(n-1\) 次操作得到

考虑先 \(dp\) 求出至少 \(i\) 次操作,生成了总长为 \(j\) 的环的种类数,合并两个环类似 \(\text{exp}\) 计算

偶数操作显然是可以抵消的,并且奇数次操作无法抵消,故还需根据奇偶性累前缀和

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

const int N=410,P=1e9+7;

int n,m;
ll qpow(ll x,ll k=P-2){
ll res=1;
for(;k;k>>=1,x=x*x%P) if(k&1) res=res*x%P;
return res;
}
int C[N][N],F[N],D[N],I[N],J[N];
int dp[N][N];

int main(){
n=rd(),m=rd();
rep(i,0,N-1) rep(j,*C[i]=1,i) C[i][j]=(C[i-1][j]+C[i-1][j-1])%P;
I[0]=I[1]=J[0]=J[1]=1;
rep(i,2,N-1) {
J[i]=1ll*J[i-1]*i%P;
I[i]=1ll*(P-P/i)*I[P%i]%P;
}
// D[i]=C(n,i)
D[0]=1;
rep(i,1,min(m*2,n)) D[i]=1ll*D[i-1]*(n-i+1)%P*I[i]%P;
rep(i,1,N-1) I[i]=1ll*I[i-1]*I[i]%P;
dp[0][0]=1;
rep(i,0,m) rep(j,0,i*2) if(dp[i][j]) rep(k,1,m-i) {
// 生成了k+1个数
// C[j+k+1][j] 组合,强制一个元素在第一位
// J[k] 环排列
dp[i+k][j+k+1]=(dp[i+k][j+k+1]+1ll*dp[i][j]*C[j+k][j]%P*J[k])%P;
}
rep(i,0,m) rep(j,0,i*2) if(dp[i][j]) F[i]=(F[i]+1ll*D[j]*dp[i][j])%P;
rep(i,2,m) F[i]+=F[i-2],Mod1(F[i]);
rep(i,1,m) printf("%d ",F[i]);
}

CF1491G - Switch and Flip

题目大意

\(n\) 个硬币,编号 \(1-n\) ,第 \(i\) 个位置上当前放了编号 \(a_i\) 的硬币

每次交换 \((a_i,a_j)i\ne j\) ,且将硬币 \(a_i,a_j\) 翻转

求方案使得最终使得 \(a_i=i\) 且每个硬币恰好为原先方向

\(n\ge 3\) ,方案步数 \(\leq n+1\)


分析

显然要先对于 \(a_i\) 求出置换环,步数 \(\leq n+1\) 说明

1.general的情况可以用 \(n\) 步解决 \(n\) 个点

2.存在至多一个特殊情况要 \(n+1\)

手玩发现我们无法 \(n\) 步解决一个大小为 \(n\) 的环

但是如果环上恰好已经有两个硬币被翻过,那么可以

QQ截图20210511180918.png

图上点表示硬币编号,箭头所指是这个硬币应该在的位置

我们从一个已经翻转的点开始,不断交换 \(i,a_i\) 上的硬币,会将 \(a_i\) 移动到到应该在的位置上

同时下一个位置被翻转

不断进行这个操作,直到这个点消去了半边环,遇到了下一个点也是被翻过的点

此时再从下一个点开始将环的另外半边消去


那么考虑如何让一个环有两个已经翻转的点

假设提取出了 \(c\) 个环,我们可以先尽量成对匹配两个环

通过一次跨过环的交换操作合并两个大小 \(x,y\) 的环,同时生成两个翻转点

然后进项上面的操作,需要 \(x+y-1\) 次,恰好一共 \(x+y\)


那么对于最后剩下的一个环

1.如果前面已经有环被匹配过

那么随便选择一个当前 \(a_i=i\) 的自环与其合并即可


2.整个图为一个大环

先通过交换 \(1,a_1\)\(a_1\) 弹出,然后 \(a_1\) 再和环上另外一个元素交换

此时 \(a_1\) 变成未翻转状态,环又并成一个环+2个翻转点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
const int N=2e5+10,INF=1e9+10;

int n;
int a[N],vis[N],b[N],c;

int X[N],Y[N],C,col[N];
void Swap(int x,int y){
X[++C]=x,Y[C]=y;
swap(a[x],a[y]),col[a[x]]^=1,col[a[y]]^=1;
}
void Solve(int i){
while(!col[a[i]]) i=a[i];
while(!col[a[a[i]]]) Swap(i,a[i]);
i=a[i];
while(i!=a[i]) Swap(i,a[i]);
}

int main(){
n=rd();
rep(i,1,n) a[i]=rd();
rep(i,1,n) if(!vis[i]) {
for(int j=i;!vis[j];j=a[j]) vis[j]=1;
b[++c]=i;
}
for(int i=1;i<c;i+=2) {
Swap(b[i],b[i+1]);
Solve(b[i]);
}
if(c&1) {
if(c==1) {
int t=a[1];
Swap(1,a[1]),Swap(t,a[1]);
Solve(i);
} else {
rep(i,1,n) if(a[i]==i) {
Swap(i,b[c]);
Solve(i);
break;
}
}
}
printf("%d\n",C);
rep(i,1,C) printf("%d %d\n",X[i],Y[i]);
}

CF1517E - Group Photo

题目大意

对于一个长度为 \(n\) 的序列,每个元素有一个权值 \(a_i\) ,现在为这个序列染色,每个不是C就是P,且满足

\(c_i-c_{i-1}\leq c_{i+1}-c_i\)

\(p_i-p_{i-1}\ge p_{i+1}-p_i\)

\[ \ \]

模型分析

由一个Simple的性质

对于 \(c\) 构成的连续段,只有第一段长度可能>1

对于 \(p\) 构成的连续段,只有最后一段长度可能>1

综合以上容易发现

中间交错段都是一个c一个p,两端可以有一段极长的

比较general的情况可以表示如下

# 1 2 3 4 5 6 7 8 9 10 11
C - - - - - (-)
P (-) - - - -

比较丑哈

side 就是P在C前面且只有一个长段

那么枚举 \(P\) 的长段开头,二分/尺取前面交错部分长度即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
typedef unsigned long long ull;
typedef double db;
typedef pair <int,int> Pii;
#define reg register
#define mp make_pair
#define pb push_back
#define Mod1(x) ((x>=P)&&(x-=P))
#define Mod2(x) ((x<0)&&(x+=P))
#define rep(i,a,b) for(int i=a,i##end=b;i<=i##end;++i)
#define drep(i,a,b) for(int i=a,i##end=b;i>=i##end;--i)
template <class T> inline void cmin(T &a,T b){ ((a>b)&&(a=b)); }
template <class T> inline void cmax(T &a,T b){ ((a<b)&&(a=b)); }

char IO;
template <class T=int> T rd(){
T s=0; int f=0;
while(!isdigit(IO=getchar())) f|=IO=='-';
do s=(s<<1)+(s<<3)+(IO^'0');
while(isdigit(IO=getchar()));
return f?-s:s;
}

const int N=2e5+10,P=998244353;

int n,a[N];
ll s[N],t[N];

int Calc(int i,ll d){
int ans=0;
int res=-1;
for(int l=0,r=(i-1)/2,mid;l<=r;)
if(mid=(l+r)>>1,d-t[i]+t[i-mid*2]-s[i-mid*2]>0) r=mid-1,res=mid;
else l=mid+1;
if(~res) ans+=(i-1)/2-res+1;
if(i==1) return ans;
res=-1;
for(int l=0,r=(i-2)/2,mid;l<=r;)
if(mid=(l+r)>>1,d-t[i]+t[i-mid*2]-s[i-mid*2]+2*a[1]>0) r=mid-1,res=mid;
else l=mid+1;
if(~res) ans+=(i-2)/2-res+1;
return ans;
}

int main(){
rep(_,1,rd()) {
n=rd();
rep(i,1,n) a[i]=rd();
rep(i,1,n) s[i]=s[i-1]+a[i];
rep(i,1,n) t[i]=a[i]-t[i-1];
ll ans=0;
rep(i,1,n-1) {
ans+=Calc(i,s[n]-s[i]);
if(i<n-1) ans+=Calc(i,s[n-1]-s[i]-a[n]);
}
rep(i,1,n) if(s[i]-(s[n]-s[i])>0) ans++;
printf("%lld\n",ans%P);
}
}

CF Round #635 Div.1 Chiori and Doll Picking (hard version)

考虑对于 \(a_i\) 建立线性基 \(d\) ,并且通过高斯消元重整,使得 \(d\) 中 每一个元素的最高位 仅自己包含

不妨设 \(k=|d|\) ,一个基底的生成集合为 \(S(d)\) ,设 \(A=S(d)\) ,预处理部分复杂度为 \(O(nm+k^2)\)

\[ \ \]

根据线性基的基本性质,我们知道任何一个 \(x\in S(d)\)\(2^{n-k}\) 种生成方法

因此我们只需要计算线性基元素异或的答案即可,这样我们将问题规模降低到了 \(k\)

\[ \ \]

\[ \ \]

暴力1

对于 \(k\leq 27\) ,暴力枚举每个元素是否选择,可以通过预处理让复杂度降至 \(O(2^k)\)


暴力2

\(m\leq 35,k>27\)

由于线性基包含 \(k\) 个关键01位, \(m-k\) 个非关键01位

通过高斯消元可以使得基的每一位仅包含一个关键01位

\(dp_{S,i}\) 表示选择了 \(i\) 个基,非关键01位异或和为 \(S\) 的方案数

复杂度为 \(O(2^{m-k}m^2)\)



对称暴力

由于 \(m\leq 53\) ,而我们能够暴力解决 \(k\leq 27\) ,则可以考虑剩下的 \(m-k\) 个位,想办法在 \(O(2^{m-k})\) 时间内求解

\[ \ \]

考虑计算个数为 \(c\) 的方案数,我们用一个卷积形式来描述,令 \(\displaystyle F_c(x)=\sum_{|T|=c}x^{T}\)

则容易发现 \(ans_c=[x^{\empty}](A\bigoplus F_c)\) ,其中 \(\bigoplus\) 表示 异或 (集合对称差) 卷积

显然我们需要 \(\text{FWT}\) 来计算这个东西,也就是计算

\([x^{\empty}]\text{FWT}(\text{FWT}(A)\cdot \text{FWT}(F_c))\)

先考虑比较复杂的 \(G=\text{FWT}(A)\) 的计算

下面你需要良好掌握 \(\text{FWT}\)参考


1: \(G(x)\) 中每一非零项系数为 \(2^k\)

考虑线性基 \(A\) 的元素是封闭的,则有 \(A\bigoplus A=A\cdot |A|\)

\(G\cdot G=G\cdot 2^k\) ,解方程得到 \([x^S]G\in\{0,2^k\}\)


2: \([x^S]G(x)=2^k\Longleftrightarrow \forall T,|S\cap T|\equiv 0\pmod 2\)

\(\text{FWT}\) 式子

\([x^S]G(x)=\sum (-1)^{|S\cap T|} [x^T]A(x)\)

\(A(x)\)\(2^k\) 个1构成,故得结论


确定非零项

由恒等式 \(|X\cap S|+|Y\cap S|\equiv|(X\oplus Y)\cap S|\pmod 2\) ,得到简化

1.若 \(X,Y\) 对于 \(S\) 合法,则 \(X\oplus Y\) 同样合法,只需要考虑线性基 \(d\) 中元素对于 \(S\) 的限制

2.假设已知 \(S,T\) 非零,则 \(S\oplus T\) 非零,因此可以考虑用一个线性基 \(d'\) 来描述合法元素


确定 \(|d'|\) 大小

\(2^kS(d')=G\)\(\text{IFWT}(2^k\cdot S(d'))=A\)

带入两边 \(x^{\empty}\) 项的值,容易得到 \(|S(d')|=2^{m-k}\) ,故 \(|d'|=m-k\)

\(|d'|=m-k\) 是接近前面猜想的一大跳跃


构造 \(d'\) ?

考虑用0/1矩阵形式描述线性基 \(d\)

\(d\) 中的元素中的最高位移动到主对角线上最高的 \(k\) 个位置,此时每一行一定是一个主对角线元素后面跟上一些位置 \(\ge k+1\) 的元素

此时 \(d'\) 的构造即:主对角线取反,其余位置为转置

\(\color{blue} 1\) 0 0 \(\color{blue} 1\) 0
0 \(\color{blue} 1\) 0 0 \(\color{blue} 1\)
0 0 \(\color{blue} 1\) \(\color{blue} 1\) 0
\(\color{red}1\) 0 \(\color{red}1\) \(\color{red}1\) 0
0 \(\color{red}1\) 0 \(\color{red}1\) \(\color{red}1\)

Proof:

显然这样构造出的 \(d'\) 元素最低位独立,因此不线性相关,只需要证明满足限制即可

首先 \(d_i\)\(d'_j\) 在主对角线上无交,有交部分一定是一个主对角线元素与一个非主对角线元素交

\(d_{i}\)\(d'_j\)\(d_{i,j},d'_{j,j}\) 有交,则在其关于主对角线对称的位置 \(d_{i,i},d'_{i,j}\) 处同样有交

因此交都是成对出现的

\[ \ \]

\[ \ \]


由此我们可以在 \(2^{m-k}\) 时间内通过暴力枚举得到 \(G\) 中每个非零项

下面考虑 \(\text{FWT}(F^c)\) 的贡献的部分实际极其简单

可以根据 \(G\) 中每一项 \(x^S\)\(|S|\) 确定 \(\text{FWT}(F^c)\)

\([x^S]\text{FWT}(F^c)=\sum_{|T|=c}(-1)^{|S\cup T|}\)

对于 \(G\) 中不同的 \(|S|\) 分类,对于 \(|T|=c\) ,枚举 \(|S\cup T|\) ,添加组合数系数即可计算贡献

注意最后求出的答案 \([x^{\empty}]\) 为对应项相乘之后求和除去 \(\text{IFWT}\)\(2^k\)

复杂度为 \(O(2^{m-k}+m^3+nm)\)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
const int N=210,P=998244353;

int n,m,c;
ll d[62],e[63],C[62][62],W[63][63];
ll qpow(ll x,ll k=P-2){
ll res=1;
for(;k;k>>=1,x=x*x%P) if(k&1) res=res*x%P;
return res;
}

vector <int> Enum(ll *d){
static ll a[N],S[1<<15],T[1<<15],bin[1<<15];
int n=0,m;
rep(i,0,61) if(d[i]) a[n++]=d[i];
m=n/2;
rep(i,0,m) bin[1<<i]=i;
rep(i,1,(1<<m)-1) S[i]=S[i&(i-1)]^a[bin[i&-i]];
rep(i,1,(1<<(n-m))-1) T[i]=T[i&(i-1)]^a[bin[i&-i]+m];
vector <int> res(::m+1);
int X=(1<<m)-1;
rep(i,0,(1<<n)-1) res[__builtin_popcountll(S[i&X]^T[i>>m])]++;
return res;
}

int main(){
n=rd(),m=rd();
rep(i,1,n) {
ll x=rd<ll>();
drep(i,m-1,0) if(x&(1ll<<i)) {
if(!d[i]) {
d[i]=x,c++;
break;
} else x^=d[i];
}
}
if(c<=27) {
n=qpow(2,n-c);
vector <int> res=Enum(d);
rep(i,0,m) res[i]=1ll*res[i]*n%P;
rep(i,0,m) printf("%d ",res[i]);
return 0;
}
rep(i,0,m) rep(j,*C[i]=1,i) C[i][j]=(C[i-1][j]+C[i-1][j-1])%P;
rep(i,0,m) { // F_i变成j之后的变化
rep(j,0,m) rep(k,0,min(i,j)) {
W[i][j]=(W[i][j]+1ll*(k&1?-1:1)*C[j][k]*C[m-j][i-k])%P;
}
}
rep(i,0,m-1) if(d[i]) rep(j,i+1,m) if(d[j]&(1ll<<i)) d[j]^=d[i];
rep(i,0,m-1) if(d[i]) {
rep(j,0,i-1) if(d[i]&(1ll<<j)) e[j]|=1ll<<i;
} else e[i]|=1ll<<i;
vector <int> t=Enum(e),ans(m+1);
n=qpow(2,n-c+c-m+P-1);
rep(i,0,m) rep(j,0,m) ans[i]=(ans[i]+1ll*W[i][j]*t[j])%P;
rep(i,0,m) ans[i]=1ll*(ans[i]+P)*n%P;
rep(i,0,m) printf("%d ",ans[i]);
}

「JOI 2018 Final」毒蛇越狱

Algorithm 1: 暴力计算

对于所有 \(0,1,?\) 组成的 \(3^n\) 种串处理出答案

具体的,对于当前串包含的最后一个 \(?\) 位置,枚举它变成0/1的答案,按照一定的顺序累和即可

(代码可以在Algo2里面看到)

Algorithm 2 : Meet in the Middle

\(3^{20}\) 太大,优化上面的暴力,容易想到把复杂度从预处理分一部分给查询

取出 \(n\) 中前 \(k\) 个位置,这些位置不处理 \(3^k\) ,而是让每个询问暴力地去枚举这些位置上的 \(?\) 变成 \(0/1\)

显然每个询问有最多 \(2^k\) 次枚举,即复杂度为 \(O(Q\cdot 2^k)\)

对于剩下的 \(n-k\) 个位置,采取上面的暴力方法预处理,三进制枚举,预处理复杂度为 \(O(2^k3^{n-k})\)

因此复杂度为 \(O(Q\cdot 2^k +2^k3^{n-k})\) ,计算在 \(k=6,7\) 时复杂度约为 \(3.5\cdot 10^8\)

(这是一个非常稳的复杂度)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
typedef unsigned long long ull;
typedef double db;
typedef pair <int,int> Pii;
#define reg register
#define pb push_back
#define mp make_pair
#define Mod1(x) ((x>=P)&&(x-=P))
#define Mod2(x) ((x<0)&&(x+=P))
#define rep(i,a,b) for(int i=a,i##end=b;i<=i##end;++i)
#define drep(i,a,b) for(int i=a,i##end=b;i>=i##end;--i)
template <class T> inline void cmin(T &a,T b){ ((a>b)&&(a=b)); }
template <class T> inline void cmax(T &a,T b){ ((a<b)&&(a=b)); }


char IO;
ll rd(){
ll s=0,f=0;
while(!isdigit(IO=getchar())) if(IO=='-') f=1;
do s=(s<<1)+(s<<3)+(IO^'0');
while(isdigit(IO=getchar()));
return f?-s:s;
}

const int N=20,M=1<<20,M3=1600000;
const int DM=7;


int n,m,k;
int Pow[N],S[M3],Low[M3],trans[M3];
int QX[M],QY[M],QZ[M],Ans[M],rev[M];
char val[M],q[N];

int main() {
rep(i,Pow[0]=1,N-1) Pow[i]=Pow[i-1]*3;
n=rd(),m=rd(),k=min(DM,n),scanf("%s",val);
rep(i,1,(1<<n)-1) {
rev[i]=(rev[i>>1]>>1)|((i&1)<<(n-1));
if(i<rev[i]) swap(val[i],val[rev[i]]);
}
Low[0]=1e9;
rep(i,1,Pow[n-k]-1) {
Low[i]=(i%3==2)?0:Low[i/3]+1;
if(Low[i]>n) trans[i]=(trans[i/3]<<1)|(i%3);
}
rep(i,1,m) {
scanf("%s",q);
rep(j,0,n-1) {
if(q[j]=='?') QY[i]|=1<<j,q[j]='2';
else QX[i]|=(q[j]-'0')<<j;
}
rep(j,k,n-1) QZ[i]+=Pow[j-k]*(q[j]-'0');
}
int A=(1<<k)-1;
rep(i,0,A) {
rep(j,0,Pow[n-k]-1) {
if(Low[j]>n) S[j]=val[(trans[j]<<k)|i]-'0';
else S[j]=S[j-Pow[Low[j]]]+S[j-2*Pow[Low[j]]];
} // 暴力预处理前缀和
rep(j,1,m) if((QX[j]&A)==(i&~QY[j])) Ans[j]+=S[QZ[j]];
}
rep(i,1,m) printf("%d\n",Ans[i]);
}

Algorithm 3 : 高位前缀和+容斥

起始学过高位前缀和/FMT的看到这个题第一反应可能都是这个。。

-> 对于 \(?\) 的位置,直接赋值成1,然后对于这个数从高位前缀和里查询

然后你发现不知道怎么对于1的把0的去掉

显然这个可以通过一个暴力的容斥来完成,枚举一些1的位置变成0,然后就是容斥的奇数减偶数加

复杂度为 \(O(Q\cdot 2^{1的个数}\ \ \ \ \ )\)

同理,处理高位后缀和,复杂度为 \(\begin{aligned}O(Q\cdot 2^{0的个数}\ \ \ \ )\end{aligned}\)

而直接暴力枚举 \(?\) 变成0/1,复杂度为 \(\begin{aligned}O(Q\cdot 2^{?的个数}\ \ \ \ \ )\end{aligned}\)

综合这三种算法,选一个更优的做,就得到一个复杂度为

\(\begin{aligned}O(Q \ \cdot 2^{ \begin{aligned}\ \ \ \ \ \ \ \ \ \ \min\lbrace 1的个数,0的个数,?的个数\rbrace\end{aligned}})\ \ \ \ \ \ \end{aligned}\)

显然查询复杂度就是 \(O(Q\cdot 2^{\lfloor \frac{n}{3}\rfloor }=Q\cdot 2^6)\)

算上预处理,复杂度为 \(O(2^nn+Q\cdot 2^6)\)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include<bits/stdc++.h>
using namespace std;
enum{N=1<<20};
int n,k,m,A[N],B[N],C[N],P[N];
// A 高位前缀和
// B 高位后缀和
// C 点值(打扰了)
// P __builtin_parity
char val[N],q[21];
int main() {
scanf("%d%d%s",&n,&m,val),k=1<<n;
for(int i=0;i<k;++i) A[i]=B[i]=C[i]=val[i]-'0',P[i]=P[i>>1]^(i&1);
for(int i=1;i<k;i<<=1) for(int l=0;l<k;l+=i*2) for(int j=l;j<l+i;++j) A[j+i]+=A[j],B[j]+=B[j+i];
//预处理高位前缀和,高位后缀和
while(m--) {
int x=0,y=0,a=0,b=0,ans=0;
for(int i=scanf("%s",q+1);i<=n;++i) {
if(q[i]=='?') x|=1<<(n-i),a++;
if(q[i]=='1') y|=1<<(n-i),b++;
}
if(a<=n/3) for(int S=x;~S;S=S?(S-1)&x:-1) ans+=C[y|S]; // 枚举?变成0/1
else if(b<=n/3) for(int S=y;~S;S=S?(S-1)&y:-1) ans+=P[S^y]?-A[S|x]:A[S|x]; // 对于高位前缀和容斥
else for(int S=x=(k-1)^x^y;~S;S=S?(S-1)&x:-1) ans+=P[S]?-B[S|y]:B[S|y]; // 对于高位后缀和容斥
printf("%d\n",ans);
}
}

「JOI 2020 Final」奥运公交 (最短路)

问题实际上就是要分别求 \(1-n\)\(n-1\) 对于每一条边翻转之后的最短路

由于 \(m\) 的上限为 \(n^2\) ,下面所说的 \(\text{Dijkstra}\) 都是没有堆优化的板本,即 \(n^2\) 找最小点, \(m\) 更新

以计算 \(1-n\) 为例

不妨先考虑计算删除每一条边 \((u,v,c)\) 之后,1为源点的的最短路情况

我们知道从源点 \(S\) 出发的最短路可以用最短路图描述,而最短路图是一张拓扑图(fix:这道题含有0边,所以并不是,但是没有关系)

如果从最短路图中提取一棵树,那么显然只有这些树边需要考虑删除之后对于最短路的影响

对于这些边重新求最短路即可,复杂度为 \(O(n(m+n^2))\)

如何考虑翻转一条边之后的贡献?

不妨再求出以 \(n\) 为结束的最短路,即求反图 \(n\) 为源点的答案

然后在两个最短路上查询一下即可得到 \(1-n\) 的最短路

同理得到 \(n-1\) 的答案

复杂度为 \(O(n(m+n^2))\)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
typedef unsigned long long ull;
typedef double db;
typedef pair <int,int> Pii;
#define reg register
#define mp make_pair
#define pb push_back
#define Mod1(x) ((x>=P)&&(x-=P))
#define Mod2(x) ((x<0)&&(x+=P))
#define rep(i,a,b) for(int i=a,i##end=b;i<=i##end;++i)
#define drep(i,a,b) for(int i=a,i##end=b;i>=i##end;--i)
template <class T> inline void cmin(T& a,T b){ ((a>b)&&(a=b)); }
template <class T> inline void cmax(T& a,T b){ ((a<b)&&(a=b)); }

char IO;
template <class T=int> T rd(){
T s=0;int f=0;
while(!isdigit(IO=getchar())) f|=IO=='-';
do s=(s<<1)+(s<<3)+(IO^'0');
while(isdigit(IO=getchar()));
return f?-s:s;
}


bool Mbe;
const int N=210,M=5e4+10,INF=2e9+10;

int n,m;
int E[N][N],E2[N][N],EI[N][N],dis[N],vis[N];
void Dijkstra(int S){
rep(i,0,n) dis[i]=INF,vis[i]=0;
dis[S]=0;
while(1) {
int u=0;
rep(i,1,n) if(!vis[i] && dis[i]<dis[u]) u=i;
if(!u) break;
vis[u]=1;
rep(i,1,n) if(E[u][i]<INF) cmin(dis[i],dis[u]+E[u][i]);
}
}

int U[M],V[M],C[M],D[M];
int mk[M];
void dfs(int u) {
vis[u]=1;
rep(i,1,n) if(!vis[i] && E[u][i]<INF && dis[i]==dis[u]+E[u][i]) mk[EI[u][i]]=1,dfs(i);
}

int Res1[M][N],Res2[M][N];
ll Ans[M];

void Solve(int S,int Res[M][N]){
// 计算删除每条边之后,S为源点的最短路情况,放在Res[M][N]中
rep(i,1,n) rep(j,1,n) E[i][j]=INF,E2[i][j]=INF;
rep(i,1,m) {
int u=U[i],v=V[i],c=C[i];
if(E[u][v]>c) E2[u][v]=E[u][v],E[u][v]=c,EI[u][v]=i;
else if(E2[u][v]>c) E2[u][v]=c;
}
Dijkstra(S);
rep(i,1,n) vis[i]=0;
rep(i,1,m) mk[i]=0;
dfs(S);
rep(i,1,m) if(!mk[i]) rep(j,1,n) Res[i][j]=dis[j];
rep(i,1,m) if(mk[i]) {
swap(E[U[i]][V[i]],E2[U[i]][V[i]]);
Dijkstra(S);
rep(j,1,n) Res[i][j]=dis[j];
swap(E[U[i]][V[i]],E2[U[i]][V[i]]);
}
}

void Solve(){
Solve(1,Res1);
rep(i,1,m) swap(U[i],V[i]);
// 反图计算
Solve(n,Res2);
rep(i,1,m) swap(U[i],V[i]);

rep(i,1,m) {
int t=min(Res1[i][n],Res2[i][1]);
if(Res1[i][V[i]]<INF && Res2[i][U[i]]<INF) cmin(t,Res1[i][V[i]]+Res2[i][U[i]]+C[i]);
Ans[i]+=t; // 合并贡献
}
}

bool Med;
int main(){
//fprintf(stderr,"%.2lf\n",(&Med-&Mbe)/1024.0/1024.0);
n=rd(),m=rd();
rep(i,1,m) U[i]=rd(),V[i]=rd(),C[i]=rd(),D[i]=rd();
ll ans=0;
rep(i,1,n) rep(j,1,n) E[i][j]=INF;
rep(i,1,m) cmin(E[U[i]][V[i]],C[i]);
Dijkstra(1),ans+=dis[n];
Dijkstra(n),ans+=dis[1];

// 计算1-n
Solve();
// 计算n-1
rep(i,1,m) U[i]=n-U[i]+1,V[i]=n-V[i]+1;
Solve();
rep(i,1,m) cmin(ans,Ans[i]+D[i]);
printf("%lld\n",ans>=INF?-1:ans);
}

CF715E - Complete the Permutations

题目大意

对于两个排列 \(p,q\) ,令 \(p\rightarrow q\) 代价为通过交换使得 \(p\) 变成 \(q\) 的最小步数

现在部分给定了 \(p\)\(q\) ,求所有情况下, \(p\rightarrow q=i,i\in[0,n-1]\) 的排列组数目


分析

排列变换显然要放到置换环上考虑,考虑两个排列之间的变换有多种等价的方式

不妨认为连的边就是 \(p_i\rightarrow q_i\) ,最终操作步数就是 \(n-\) 置换环的个数

对于已经确定的部分,能够确定的边可以直接连,能够确定的链可以缩成点

那么最终,图上只剩下三种待定的边

\(0\rightarrow 0,0\rightarrow x,x\rightarrow 0\) ,其中 \(0\rightarrow x,x\rightarrow 0\) 表示一条出现了一半的边

ps: 如果有 \(0\rightarrow x\rightarrow 0\) ,那么直接缩成一个 \(0\rightarrow 0\) 看待

不妨设这三种边个数分别为 \(A,B,C\) ,已经确定的环可以数出是 \(D\) 最后加入答案

由于一个 \(A\) 由两边确定,实际上确定一个边组之后,排列 \(0\rightarrow 0\) 的位置得到 \(A!\) 种方案,也可以最后加入答案

考虑什么样的边可以接成环

仅A: \(0\rightarrow 0,0\rightarrow 0\cdots\)

仅B: \(0\rightarrow x,0\rightarrow x\cdots\)

仅C: \(x\rightarrow 0,x\rightarrow 0,\cdots\)

A+B=A, \(0\rightarrow x+0\rightarrow 0=0\rightarrow 0\)

C+A=A, \(0\rightarrow 0+x\rightarrow 0=0\rightarrow 0\)

实际上,组合环的情况

B前面要么是B要么是A,最终将A后面跟着的小弟B都缩在一起看待

C后面要么是C要么是A,最终将A前面跟着的大哥C都缩在一起看待

实际上B,C计算类似,我们能够得到一个计算思路

将每个B,C加入组合环对于组合环缩点之后的点数无影响,那么可以将A,B,C分离计算

那么考虑一个B要么在单纯的B环上要么在组合环上

枚举有 \(i\)\(B\) 在单纯B环上,构成 \(j\) 个环的方案数(当然要先组合数将 \(j\) 个点选出)

这就是第一类斯特林数 \(\begin{bmatrix}i\\j\end{bmatrix}\)参考

剩下的加入组合环中,考虑依次加入每个B,每个B可以接在B后面也可以接在A后面

方案数即 \(A^{\overline{B-i}}\) ,最终计算得到 \(G_i\) 表示B构成了i个单纯B环的方案数,复杂度为 \(O(n^2)\)


A的贡献不需要将组合环和单纯A环分开考虑,直接就是 \(F_i=\begin{bmatrix}A\\i\end{bmatrix}\)

最后将三种点背包合并,加入前面提到的常量即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#include<bits/stdc++.h>
using namespace std;
#define rep(i,a,b) for(int i=a,i##end=b;i<=i##end;++i)
enum{N=300,P=998244353};
int n;
int p[N],q[N],pre[N],nxt[N],A,B,E,D;
int F[N],G[N],H[N],V[N];
int S[N][N],T[N][N],C[N][N];
int main(){
scanf("%d",&n);
rep(i,1,n) pre[i]=nxt[i]=-1;
rep(i,**S=1,n) rep(j,1,i) S[i][j]=(S[i-1][j-1]+1ll*(i-1)*S[i-1][j])%P;
rep(i,0,n) rep(j,*T[i]=1,n) T[i][j]=1ll*T[i][j-1]*(i+j-1)%P;
rep(i,0,n) rep(j,*C[i]=1,i) C[i][j]=(C[i-1][j]+C[i-1][j-1])%P;
rep(i,1,n) scanf("%d",p+i);
rep(i,1,n) {
scanf("%d",q+i);
if(p[i] && q[i]) nxt[p[i]]=q[i],pre[q[i]]=p[i];
else if(p[i]) nxt[p[i]]=0;
else if(q[i]) pre[q[i]]=0;
}
rep(i,1,n) if(pre[i]<=0) {
int j=i;
for(;nxt[j]>0;j=nxt[j]) V[j]=1;
V[j]=1;
if(pre[i]==nxt[j]) A+=pre[i]==-1; // ==0 || ==-1 ,but we can't count 0 in
else if(~pre[i]) B++;
else E++;
}
rep(i,1,n) if(!V[i]) {
for(int j=i;!V[j];j=nxt[j]) V[j]=1;
D++;
}
int c=1;
rep(i,1,A) c=1ll*c*i%P;
rep(i,0,A) F[i]=1ll*c*S[A][i]%P;
rep(i,0,B) rep(j,0,i) G[j]=(G[j]+1ll*S[i][j]*T[A][B-i]%P*C[B][i])%P;
rep(i,0,E) rep(j,0,i) H[j]=(H[j]+1ll*S[i][j]*T[A][E-i]%P*C[E][i])%P;

rep(i,0,n) V[i]=0;
rep(i,0,A) rep(j,0,B) V[i+j+D]=(V[i+j+D]+1ll*F[i]*G[j])%P;
rep(i,0,n) F[i]=0;
rep(i,0,A+B+D) rep(j,0,E) F[n-i-j]=(F[n-i-j]+1ll*V[i]*H[j])%P;
rep(i,0,n-1) printf("%d ",F[i]);
}


进一步的优化?

\(F_i\) 的计算时标准的第一类斯特林数行,用倍增法求上升幂即可

\(\displaystyle G(x)=\sum_{i=0}^B A^{\overline{B-i}}\binom{B}{i} x^{\overline{i}}\)

把系数拿出来,可以直接做一个上升幂多项式转普通多项式

复杂度为 \(O(n\log ^2n)\)

(听说可以 \(O(n\log n)\) 但是我没有脑子只会套板子哈哈哈哈


以下未上传!!!如果看到说明我在搞笑!!!看到叫我

\(\displaystyle G_i=\sum_{j=i}^B \binom{B}{j}A^{\overline{B-j}}\cdot [x^j]\frac{1}{i!}(-1)^i\ln^i(1-x)\)

\(\displaystyle G_i=\sum_{j=i}^B \frac{B!}{j!(B-j)!}\frac{(A+B-j-1)!}{(A-1)!}\cdot [x^j]\frac{1}{i!}(-1)^i\ln^i(1-x)\)

\(\displaystyle G_i=\frac{(-1)^iB!}{(A-1)!i!} \sum_{j=i}^B \frac{(A+B-j-1)!}{j!(B-j)!}\cdot [x^j]\ln^i(1-x)\)

由于对于每个 \(i\)\(\displaystyle \sum_{j=i}^B \frac{(A+B-j-1)!}{j!(B-j)!}\) 是常量,设

\(\displaystyle \varphi(x)=\sum_{j=0}^B x^{-1-j} \frac{(A+B-j-1)!}{j!(B-j)!}\) (为什么是是 \(-1-j\) 会在下面出现)

\(\displaystyle G_i=\frac{(-1)^iB!}{(A-1)!i!} [x^{-1}] \varphi(x)\ln^i(1-x)\)

对比扩展拉格朗日反演的形式

\(\displaystyle [x^n]H(G(x))=\frac{1}{n}[x^{-1}]H'(x)(\frac{1}{F(x)})^n\) ,其中 \(G(x)\)\(F(x)\) 的复合逆

得到 \(\displaystyle H(x)=\int \varphi(x),F(x)=\frac{1}{\ln(1-x)}\)

从而得到 \(F(x)\) 的复合逆为 \(\displaystyle 1-e^{x^{-1}}\)

现在要算 \(H(1-e^{x^{-1}})=\sum\)

「JOI 2021 Final」机器人

原问题中颜色什么时候改没有影响

显然不能记录每条边的颜色,显然在行走过程中不会回到原先的点

因此考虑简化状态

从一个点出去时,走了一条边 \((u,v,c,w)\) ,从 \(u\) 出发颜色为 \(c\) 的边 \(w\) 之和为 \(S_{u,c}\) ,则有两种转移:

1.走过来时的边被改变了,权值 \(w\)

2.改变了其他同种颜色的边,权值 \(S_{u,c}-w\)

对于2情况在前面修改的边,在后面不会产生贡献(否则可以直接通过这条边过去,而不需要绕路)

可以直接转移到对应节点

1类转移的贡献相当于下次走这种颜色时,可以少改变一条边的颜色

即下一次转移2时,权值变为 \(S_{v,c}-w-w'\)

可以增加一个额外权值 \(-w\) ,然后对于下一个点 \(v\) 所有颜色为 \(c\) 的出边转移,这可以通过构建虚点解决

实际上总权值就是 \(0\)

考虑到一种不合法的情况,即走回 \(u\) ,但是这样的转移权值就是 \(S_{v,c}-w'\ge 0\)

非法情况无影响

最终得到的转移就是一个非负边权的最短路图,可以跑 \(Dijkstra\) 解决

总点数 \(\leq n+2m\) ,总边数 \(\leq 6m\)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
#define pb push_back
#define rep(i,a,b) for(int i=a,i##end=b;i<=i##end;++i)

char IO;
int rd(){
int s=0;
while(!isdigit(IO=getchar()));
do s=(s<<1)+(s<<3)+(IO^'0');
while(isdigit(IO=getchar()));
return s;
}

const int N=5e5+10,INF=1e9+10;

int n,m,k;
struct Edge{
int v,c,w;
};
vector <Edge> G[N];
int U[N],V[N],C[N],W[N];

struct Node{
int u; ll d;
bool operator < (const Node __) const {
return d>__.d;
}
};
priority_queue <Node> que;
ll dis[N],S[N];
void Upd(int u,ll d){
if(dis[u]<=d) return;
dis[u]=d,que.push((Node){u,d});
}
map <int,int> st[N];
void AddEdge(int u,int v,int c,int w){
if(!st[u].count(c)) {
st[u][c]=++k;
G[u].pb((Edge){k,0,0});
}
int t=st[u][c];
G[t].pb((Edge){v,c,w}),S[t]+=w;
}

void Dijkstra(){
memset(dis,63,sizeof dis);
dis[1]=0,que.push((Node){1,0});
while(!que.empty()) {
int u=que.top().u; ll d=que.top().d; que.pop();
if(dis[u]<d) continue;
if(u<=n) {
for(Edge dd:G[u]) {
int t=dd.v;
for(Edge i:G[t]) {
Upd(i.v,d+min((ll)i.w,S[t]-i.w));
Upd(st[i.v][i.c],d);
}
}
} else for(Edge i:G[u]) Upd(i.v,d+S[u]-i.w);
}
printf("%lld\n",dis[n]<1e17?dis[n]:-1);
}

int main(){
freopen("robot.in","r",stdin),freopen("robot.out","w",stdout);
n=rd(),m=rd(),k=n;
rep(i,1,m) {
int u=rd(),v=rd(),c=rd(),w=rd();
AddEdge(u,v,c,w),AddEdge(v,u,c,w);
}
Dijkstra();
}
0%