生成函数、组合数学

[ARC089D] ColoringBalls

咕着
咕着

天,感觉全是细节,事实上也如此:
借大佬的细节才过了此题

#include<bits/stdc++.h>
#define maxn 75
#define rep(i,j,k) for(int i=(j),LIM=(k);i<=LIM;i++)
#define per(i,j,k) for(int i=(j),LIM=(k);i>=LIM;i--)
#define mod 1000000007
#define vi vector<int>
#define pb push_back
using namespace std;

int n,K,fac[maxn << 2],invf[maxn << 2],inv[maxn << 2];
int C(int a,int b){ if(a<0 || b<0 || a-b<0) return 0;return fac[a] * 1ll * invf[b] % mod * invf[a-b]%mod; }
char s[maxn];
vi p;int ans;

bool check(){
	static bool vis[maxn];
	static int loc[maxn];
	memset(vis,0,sizeof vis);
	int j=1;
	for(int i=p.size()-1;i>=0 && p[i];i--){
		for(;j <= K && s[j] != 'r';j++);
		if(j > K) return 0;
		vis[j] = 1 , loc[i] = j , j++;
	}
	j=1;
	for(int i=p.size()-1;i>=0 && p[i];i--){
		j = max(j , loc[i]);
		for(;j <= K && s[j] != 'b';j++);
		if(j > K) return 0;
		vis[j] = 1 , loc[i] = j , j++;
	}
	j=1;
	for(int i=0;i<p.size() && p[i]==0;i++){
		for(;j <= K && (s[j] != 'r' || vis[j]);j++);
		if(j > K) return 0;
		vis[j] = 1 , j++;
	}
	j=1;
	for(int i=p.size()-1;i>=0 && p[i] > 1;i--){
		j = max(j , loc[i]);
		rep(k,2,p[i]){
			for(;j <= K && vis[j];j++);
			if(j > K) return 0;
			vis[j] = 1 , j++;
		}
	}
	return 1;
}

void dfs(int sz,int mx,int lim){
	if(sz > n) return;
	if(check()){
		int sm = fac[p.size()];
		for(int i=0,j;i<p.size();i=j){
			for(j=i;j<p.size() && p[j] == p[i];j++);
			sm = 1ll * sm * invf[j-i] % mod;
		}
		ans = (ans + 1ll * C(n-sz+mx-1,mx-1) * sm) % mod;
		//printf("%d %d %d %d\n",sm,ans,sz,mx);
	}
	else return;
	rep(i,lim,(n-sz+1)/2){
		p.pb(i);
		dfs(sz+max(2*i-1,1)+(sz!=0),mx+2*i+2,i);
		p.pop_back();
	}
}

int main(){//freopen("1.in","r",stdin);//freopen("2.out","w",stdout);
	scanf("%d%d",&n,&K);
	scanf("%s",s+1);
	fac[0] = inv[0] = inv[1] = fac[1] = invf[0] = invf[1] = 1;
	rep(i,2,(maxn << 2) - 1) fac[i] = 1ll * fac[i-1] * i % mod , inv[i] = 1ll * (mod - mod / i) * inv[mod % i] % mod,
		invf[i] = 1ll * invf[i-1] * inv[i] % mod;
	dfs(0,1,0);
	printf("%d\n",(ans+mod)%mod);
}

[ARC096C] Everything on It

在这里插入图片描述
容斥,枚举有 k k 种元素用了 1 \leq 1 次,其中有 j j 种元素用了 1 1 次。
则答案为:
k = 0 n ( 1 ) k ( n k ) 2 2 n k j = 0 k ( k j ) p = 0 j { j p } 2 ( n k ) p \sum_{k=0}^n(-1)^k\binom nk2^{2^{n-k}}\sum_{j=0}^k \binom kj\sum_{p=0}^j \begin{Bmatrix}j\\p\end{Bmatrix}2^{(n-k)p}
注意 2 2 n k 2^{2^{n-k}} ( 2 2 ) n k (2^2)^{n-k} 是完全不同的。
注意到有恒等式 j = p k ( k j ) { j p } = { k + 1 p + 1 } \sum_{j=p}^k\binom kj \begin{Bmatrix} j\\p \end{Bmatrix}=\begin{Bmatrix} k+1\\p+1 \end{Bmatrix}
证明:考虑组合意义,相当于是拿 k j k-j 个数不选,剩下的 j j 个数分到 p p 组内,如果我们加一个组来装着 k j k-j 个数,那么这个组可能为空,同时发现这个组和别的组不一样,但是第二类斯特林数组之间是没有标号区别的,所以我们就往空组加入一个 0 0 (强行规定 0 0 所在的组为不选组),则非空和有区别这两个限制都被满足了,方案数就是 { k + 1 p + 1 } \begin{Bmatrix} k+1\\p+1 \end{Bmatrix}
但是其实这样做很傻,直接类似第二类斯特林数 d p dp
g i , j = g i 1 , j 1 + ( j + 1 ) g i 1 , j g_{i,j} = g_{i-1,j-1} + (j+1)g_{i-1,j}
后面那个 j + 1 j+1 中的 + 1 +1 就代表了不选这一选项,直接 d p dp 也没有什么问题。

a n s = k = 0 n ( 1 ) k ( n k ) 2 2 n k p = 0 k { k + 1 p + 1 } 2 ( n k ) p ans = \sum_{k=0}^n(-1)^k\binom nk2^{2^{n-k}}\sum_{p=0}^k \begin{Bmatrix}k+1\\p+1\end{Bmatrix}2^{(n-k)p}

A C   C o d e \mathcal AC \ Code

#include<bits/stdc++.h>
#define maxn 3005
#define rep(i,j,k) for(int i=(j),LIM=(k);i<=LIM;i++)
#define per(i,j,k) for(int i=(j),LIM=(k);i>=LIM;i--)
using namespace std;

int n,P,S[maxn][maxn],C[maxn][maxn],pw[maxn];
int upd(int x){ return x += x >> 31 & P; }
int main(){
	scanf("%d%d",&n,&P);
	rep(i,S[0][0]=C[0][0]=1,n) rep(j,C[i][0]=1,i) C[i][j] = upd(C[i-1][j-1] + C[i-1][j] - P) % P;
	rep(i,1,n) rep(j,S[i][0]=1,i) S[i][j] = (S[i-1][j] * (j+1ll) + S[i-1][j-1]) % P;
	int ans = 0 , ppw = 2;
	rep(i,0,n) pw[i] = 1;
	per(k,n,0){
		int sm = 0;
		rep(i,0,k) sm = (sm + 1ll * S[k][i] * pw[i]) % P;
		ans = (ans + (k&1?-1ll:1ll)*C[n][k]*ppw%P*sm)%P;
		int p2 = 1;
		rep(i,0,n) pw[i] = 1ll * p2 * pw[i] % P , p2 = 2ll * p2 % P;
		ppw = ppw * 1ll * ppw % P;
	}
	printf("%d\n",(ans+P)%P);
}

CF1295F Good Contest

题意: a i a_i 为在 [ L i , R i ] [L_i,R_i] 之间均匀随机的离散变量,求 a i a_i 不增的概率。

先离散化,将 L , R L,R 离散化后排序得到一个数组 a a
f i , j f_{i,j} 表示前 i i 个变量最后一个变量 a j \geq a_j 的概率。
那么 f i , j = f k , j + 1 × ( a j + 1 a j + i k 1 i k ) f_{i,j} = \sum f_{k,j+1} \times \binom {a_{j+1}-a_j+i-k-1}{i-k}
后面那个组合数就是在 [ a j , a j + 1 ) [a_j,a_{j+1}) 中选择 j k j-k 个可以相同的数但是需要无序的方案。(因为最后分配给 k + 1... i k+1...i 的变量是有序的。)
注意这个转移必须要 k + 1... i k+1...i 都包含 [ a j , a j + 1 ) [a_j,a_{j+1}) 这个区间才行。

A C   C o d e \mathcal AC \ Code

#include<bits/stdc++.h>
#define maxn 105
#define rep(i,j,k) for(int i=(j),LIM=(k);i<=LIM;i++)
#define per(i,j,k) for(int i=(j),LIM=(k);i>=LIM;i--)
#define mod 998244353
using namespace std;

int n,l[maxn],r[maxn],sb[maxn<<1];
int C[maxn][maxn],inv[maxn],f[maxn][maxn],in[maxn][maxn];
int Pow(int b,int k){ int r=1;for(;k;k>>=1,b=1ll*b*b%mod) if(k&1) r=1ll*r*b%mod; return r; }

int main(){
	inv[0] = inv[1] = 1;
	int sm = 1;
	for(int i=2;i<maxn;i++) inv[i] = 1ll * (mod - mod / i) * inv[mod % i] % mod;
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
		scanf("%d%d",&l[i],&r[i]),sb[++sb[0]]=l[i],sb[++sb[0]]=(++r[i]) , sm = 1ll * sm * Pow(r[i] - l[i] , mod-2) % mod;
	sort(sb+1,sb+1+sb[0]);
	sb[0] = unique(sb+1,sb+1+sb[0])-sb-1;
	for(int i=1;i<sb[0];i++){
		int L = sb[i+1] - sb[i] , f = 1;
		rep(j,1,n){
			f = 1ll * f * (L+j-1) % mod * inv[j] % mod;
			C[i][j] = f;
		} 
	}
	rep(i,1,sb[0]) f[0][i] = sm;
	for(int i=1;i<=n;i++){
		l[i] = lower_bound(sb+1,sb+1+sb[0],l[i])-sb , r[i] = lower_bound(sb+1,sb+1+sb[0],r[i])-sb;
		rep(j,l[i],r[i]-1){
			in[i][j] = 1;
			for(int k=i-1;in[k+1][j] && k>=0;k--)
				f[i][j] = (f[i][j] + 1ll * f[k][j+1] * C[j][i-k]) % mod;
		}
		per(j,sb[0]-1,1) f[i][j] = (f[i][j] + f[i][j+1]) % mod;
	}
	printf("%d\n",(f[n][1]+mod)%mod);
}

Valentines Day Contest 2020 C. Isolation

给出 ( x , y ) (x,y) ,求走 n n 步(每步可以让横坐标或纵坐标 ± 1 \pm 1 )的方案数使得途中不经过距离原点曼哈顿距离为 D 4 D \leq 4 的点。

容斥,设 f i , j , k f_{i,j,k} 表示走了 i i 步之后到达了 j j 号禁止点(禁止点为距离原点曼哈顿距离为 D D 的点集),至少一共走过了 k k 个禁止点。

所以答案就是 4 n + ( 1 ) k f i , j , k 4 n i 4^n + \sum (-1)^k f_{i,j,k}4^{n-i}
注意到我们直接计算容斥系数可以不需要 k k 这一维。
所以 f i , j f_{i,j} 在我们可以 O ( 1 ) O(1) 直接计算两个点之间走 p p 步到达的方案数后,
复杂度就是 O ( n 2 D 2 ) O(n^2D^2) 的。
设一个点在 ( a , b ) (a,b) 另一个点在 ( c , d ) (c,d) ,一共走 p p 步要到达。
发现因为有相反的方向(减少的方向)所以计数很困难,考虑如何用上一共走 p p 步的条件。

如果我们把减少横坐标看做增加纵坐标,减少纵坐标看做增加横坐标,
那么横坐标的增加量比纵坐标的增加量大 c a + b d c-a+b-d ,总步数又是 p p ,所以我们可以得出增加纵坐标的次数是 p ( c a + b d ) 2 \frac {p - (c-a+b-d)}2 ,方案数为 ( p p ( c a + b d ) 2 ) \binom {p}{\frac {p - (c-a+b-d)}2}
但是这样无法区分增加纵坐标和减少横坐标这两种操作(等),
于是我们再反着定义:
把减少横坐标看做增加横坐标,减少纵坐标看做增加横坐标,增加横坐标看做增加纵坐标,增加纵坐标不变。
那么纵坐标的增加量比横坐标的增加量大 c a + d b c-a+d-b
方案数为 ( p p ( c a + d b ) 2 ) \binom {p}{\frac {p - (c-a+d-b)}2}
然后发现这样定义,两个组合数的乘积就是我们需要的答案。
如果我们记横坐标之差为 x x ,纵坐标之差为 y y 的话答案就是:
( p p x y 2 ) ( p p x + y 2 ) \binom{p}{\frac {p-x-y}2} \binom{p}{\frac {p-x+y}2}
这个方法感觉有点奇怪,特别是拓展到三维的时候他是四个组合数乘起来。
但是他可以 O ( 1 ) O(1)

A C   C o d e \mathcal AC \ Code

#include<bits/stdc++.h>
#define maxn 3005
#define rep(i,j,k) for(int i=(j),LIM=(k);i<=LIM;i++)
#define per(i,j,k) for(int i=(j),LIM=(k);i>=LIM;i--)
#define mod 1000000007
using namespace std;

int X,Y,n,D,c[maxn][maxn];
int Pow(int b,int k){ int r=1;for(;k;k>>=1,b=1ll*b*b%mod) if(k&1)r=1ll*r*b%mod;return r; }
int C(int n,int m){ if(m < 0 || n < 0 || n - m < 0) return 0; return c[n][m]; }
int B(int a,int b,int p){
	if(p-a-b&1) return 0;
	return C(p,(p-a-b)/2) * 1ll * C(p,(p-a+b)/2) % mod;
}
int x[maxn],y[maxn],cnt;
int f[maxn][maxn];

int main(){
	scanf("%d%d%d%d",&X,&Y,&n,&D);
	rep(i,c[0][0]=1,maxn-1) rep(j,c[i][0]=1,i) c[i][j] = (c[i-1][j-1] + c[i-1][j]) % mod;	
	rep(i,-D,D) rep(j,-D,D) if(abs(i) + abs(j) == D)
		x[++cnt] = i , y[cnt] = j;
	rep(i,1,n) rep(j,1,cnt){
		f[i][j] = -B(abs(x[j]-X),abs(y[j]-Y),i);
		rep(k,1,i-1) rep(p,1,cnt)
			f[i][j] = (f[i][j] - 1ll * f[k][p] * B(x[j]-x[p],y[j]-y[p],i-k)) % mod;
	}
	int ans = Pow(4 , n);
	rep(i,1,n) rep(j,1,cnt) ans = (ans + f[i][j] * 1ll * Pow(4,n-i)) % mod;
	printf("%d\n",(ans+mod)%mod);
}

AGC034F RNG and XOR

A i A_i 概率选 i i 来异或你手上的数,问第一次得到 1.... 2 n 1 1....2^n-1 的期望时间。

怎么感觉ZJOI2019开关就是把这道题的FWT拆了拆式子快速实现。
发现多次到达同一个数很难解决,但是发现如果倒过来我们求从 1... 2 n 1 1...2^n-1 第一次到 0 0 的时间并且让 0 0 不能转移出来就可以解决多次到达同一个数的问题,因为这样同一个数都变成了 0 0 ,便于统一处理。
所以设 f i f_i 表示从 i i 第一次到 0 0 的时间。
则有 f i = j f j × A j i + 1 f_i = \sum_{j} f_j \times A_{j\wedge i} + 1 ,注意这个式子是对 i i 进行转移,所以 i > 0 i \gt 0
写成生成函数就是 ( f 0 , f 1 . . . f 2 n 1 ) ( A 0 , A 1 . . . A 2 n 1 ) = ( f 0 + 2 n 1 , f 1 1 , f 2 1.... f 2 n 1 1 ) (f_0,f_1...f_{2^n-1}) \oplus(A_0,A_1...A_{2^n-1}) = (f_0+2^n-1,f_1-1,f_2-1....f_{2^n-1}-1)
注意 f 0 + 2 n 1 f_0+2^n-1 不是由上面的方程得到的,而是因为 A A 的和为 1 1 ,所以卷积之后所有项的和不变,所以解出来的。
那么我们将 A 0 A_0 变成 A 0 1 A_0-1 则可以发现。 ( f 0 , f 1 . . . f 2 n 1 ) ( A 0 1 , A 1 . . . A 2 n 1 ) = ( 2 n 1 , 1 , 1.... 1 ) (f_0,f_1...f_{2^n-1}) \oplus(A_0-1,A_1...A_{2^n-1}) = (2^n-1,-1,-1....-1)
直接写 F W T FWT 除法即可,注意到 A i 1 = 0 \sum A_i - 1 = 0 也就是说我们 F W T FWT 后被除的式子中 2 n 1 2^n-1 这项为 0 0 (因为 A i 1 A_i \geq 1 别的都不为 0 0 ),注意到这东西表达的方程是 f 0 + f 1 + . . . f 2 n 1 × 0 = 0 f_0+f_1+...f_{2^n-1} \times 0 = 0 ,我们考虑就这样让 f 0 + f 1 + . . . f 2 n 1 = 0 f_0+f_1+...f_{2^n-1}=0 ,发现这个方程可能会无解,因为我们加多了条件,但是没关系,我们解方程是用 I F W T IFWT 解,不会考虑无解。
I F W T IFWT 之后如果得到了 f 0 , f 1 , f 2 . . . f 2 n 1 f_0',f_1',f_2'...f_{2^n-1}' ,考虑怎么得到 f 0 , f 1 . . . f 2 n 1 f_0,f_1...f_{2^n-1} ,假设真实的 f 0 + f 1 + . . . f 2 n 1 = X f_0+f_1+...f_{2^n-1} = X
那么在 F W T FWT 之后 f i = f i X 2 n f_i' = f_i - \frac {X}{2^n} ,因为 f 0 = f 0 X 2 n = X 2 n f_0' = f_0 - \frac X{2^n} = -\frac X{2^n}
所以每个位置都减去 f 0 f_0' 即可。

A C   C o d e \mathcal AC \ Code

#include<bits/stdc++.h>
#define maxn 1<<18|5
#define mod 998244353
#define rep(i,j,k) for(int i=(j),LIM=(k);i<=LIM;i++)
#define per(i,j,k) for(int i=(j),LIM=(k);i>=LIM;i--)
using namespace std;

int n,N;
int a[maxn],b[maxn];

int Pow(int b,int k){ int r=1;for(;k;k>>=1,b=1ll*b*b%mod) if(k&1) r=1ll*r*b%mod; return r; }
void FWT(int *a){
	rep(i,0,n-1) rep(j,0,N-1) if(!(j>>i&1)){
		int v = j ^ (1 << i) , x = (a[j] + a[v]) % mod , y = (a[j] - a[v]) % mod;
		a[j] = x , a[v] = y;
	}
}

int main(){
	scanf("%d",&n);N = 1 << n;int S=0;
	rep(i,0,N-1) scanf("%d",&a[i]),S+=a[i];
	S = Pow(S , mod-2);
	rep(i,0,N-1) a[i] = 1ll * a[i] * S % mod;
	a[0]-- , b[0] = 1 << n;
	rep(i,0,N-1) b[i]--;
	FWT(a),FWT(b);
	rep(i,0,N-1) a[i] = 1ll * Pow(a[i],mod-2) * b[i] % mod;
	FWT(a);int ivN = Pow(N , mod-2);
	rep(i,0,N-1) a[i] = 1ll * a[i] * ivN % mod;
	per(i,N-1,0) a[i] = (a[i] - a[0]) % mod;
	rep(i,0,N-1) printf("%d\n",(a[i]+mod)%mod);
}

2019-2020 XX Opencup GP of Tokyo E . Count Modulo 2

给出 A 1 . . . A K A_1...A_K ,求 a 1 + a 2 . . . + a n = S a_1+a_2...+a_n = S 的方案数 m o d 2 \bmod 2 ,其中 a i a_i A 1 . . . A k A_1...A_k 中的一个, K 200 A i 1 e 5 , n 1 e 18 , S 1 e 18 K\leq 200 , A_i \leq 1e5, n\leq 1e18 , S \leq 1e18

活生生猜出来的解法
因为要 m o d 2 \bmod 2 ,所以假设说一个方案中 A 1 . . . A k A_1...A_k 各有 b 1 . . b k b_1..b_k 个,那么和他只是顺序不同的方案数有 n ! b i ! 1 \frac {n!}{\prod b_i!}-1 种。
由库默尔定理我们可以知道一个二进制位 k k 只能由一个 b i b_i 所拥有,这样就可以解决 n 1 e 18 n \leq 1e18 的问题,把 n n 的每个二进制位拿出来即可。(有个更牛逼的结论,在 ( m o d 2 ) \pmod 2 意义下 f ( x 2 ) f ( x ) 2 f(x^2) \equiv f(x)^2
但是 S 1 e 18 S \leq 1e18
考虑 A i 1 e 5 A_i \leq 1e5 ,所以当我们在考虑第 k k 位的时候,剩余的 S > 2 k 1 e 5 S \gt2^k1e5 后面都无法让 S S 变为 0 0
所以我们从大到小枚举 k k ,用 b i t s e t bitset 保存 1 e 5 1e5 位,然后转移, k k-- 的时候需要枚举每个 b i t s e t bitset 位来转移,时间复杂度 O ( log n ( K v 32 + v ) ) O(\log n(\frac {Kv}{32}+v))

A C   C o d e \mathcal AC \ Code

#include<bits/stdc++.h>
#define maxn 200005
#define rep(i,j,k) for(int i=(j),LIM=(k);i<=LIM;i++)
#define per(i,j,k) for(int i=(j),LIM=(k);i>=LIM;i--)
#define LL long long
using namespace std;

LL n,S;
int a[maxn],K;
bitset<maxn>f[2],t;

int main(){	
	int T;
	for(scanf("%d",&T);T--;){
		scanf("%lld%lld%d",&n,&S,&K);
		rep(i,1,K) scanf("%d",&a[i]);
		int now = 1 , pre = 0;
		f[now].reset(),f[pre].reset();
		f[now][0] = 1;
		per(i,60,0){
			swap(now,pre);
			t.reset(),f[now].reset();
			if(n >> i & 1){
				rep(j,1,K) t ^= f[pre] >> a[j];
			}
			else t = f[pre];
			if(i){
				rep(j,0,(maxn-4)/2) if(t[j])
					f[now].flip(j << 1 | (S >> (i-1) & 1));
			}
			else 
				f[now] = t;
		}
		cout << f[now][0] << endl;
	}
}

2019-2020 XX Opencup GP of Tokyo I. Amidakuji

K log 2 n + 1 K \leq \lceil \log_2n \rceil + 1 1... n 1...n 的排列的映射 p i ( x ) p_i(x) ,对于所有 x , y x,y 都存在 q K ( q K 1 ( . . . q 1 ( x ) ) ) = y q_K(q_{K-1}(...q_1(x))) = y ,其中 q i ( x ) = p i ( x ) q_i(x) = p_i(x) p i ( x ) 1 p_i(x)^{-1}

P = log 2 n P = \lfloor \log_2n \rfloor
则我们让 p i , j = j + 2 i m o d n , i [ 0 , P ] p_{i,j} = j + 2^i \bmod n,i \in [0,P]
这样可以组合出来 n n 以内所有奇数,包括负的。
证明可以考虑归纳证明。
如果 n n 是奇数,那么正负两边已经能让我们到达所有位置。
如果 n n 是四的倍数,可以构造排列 { 2 , 3 , 0 , 1 , 6 , 7 , 4 , 5.... n 2 , n 1 , n 4 , n 3 } \{2,3,0,1,6,7,4,5....n-2,n-1,n-4,n-3\} 来让我们可以使得一个位置移动奇数或偶数,注意这时这个排列是可以让 x , y x,y 的奇偶性不同的,所以 2 P 2^{P} 这个排列就不需要了(刚好卡进),我们只需要找距离最近也就是 < 2 P \lt 2^P 的那边走。
如果 n n m o d 4 = 2 \bmod 4 = 2 ,可以构造排列 { 2 , 3 , 0 , 1 , 6 , 7 , 4 , 5.... n 4 , n 3 , n 6 , n 5 , n 2 , n 1 } \{2,3,0,1,6,7,4,5....n-4,n-3,n-6,n-5,n-2,n-1\} { 0 , 1 , 2 , 3 , . . . . n 6 , n 5 , n 2 , n 1 , n 3 , n 4 } \{0,1,2,3,....n-6,n-5,n-2,n-1,n-3,n-4\} 即可。
A C   C o d e \mathcal AC \ Code

#include<bits/stdc++.h>
#define maxn 1005
#define rep(i,j,k) for(int i=(j),LIM=(k);i<=LIM;i++)
#define per(i,j,k) for(int i=(j),LIM=(k);i>=LIM;i--)
#define LL long long
#define pb push_back
using namespace std;

int n;
vector<vector<int> >p;

int main(){
	scanf("%d",&n);
	if(n == 2){
		puts("-1");
		return 0;
	}
	int L = 1 , l = 0;
	for(;L <= n; L<<=1,l++);
	l--;
	rep(i,0,l-(n % 2 == 0)){
		vector<int>r;
		rep(j,0,n-1)  r.pb((j+(1<<i)) % n);
		p.pb(r);
	}
	if(n % 4 == 0){
		vector<int>r;
		rep(j,0,n/4-1)
			r.pb(4*j+2),r.pb(4*j+3),r.pb(4*j+1),r.pb(4*j);
		p.pb(r);
	}
	else if(n % 2 == 0){
		vector<int>r;
		rep(j,0,n/4-1)
			r.pb(4*j+2),r.pb(4*j+3),r.pb(4*j+1),r.pb(4*j);
		r.pb(n-2),r.pb(n-1);
		p.pb(r);
		r.clear();
		rep(j,0,n-5) r.pb(j);
		r.pb(n-4+2),r.pb(n-4+3),r.pb(n-4+1),r.pb(n-4);
		p.pb(r);
	}
	printf("%d\n",p.size());
	rep(i,0,p.size()-1)
		rep(j,0,n-1)
			printf("%d%c",p[i][j]+1," \n"[j==n-1]);
}

CodeForces 1292F Nora’s Toy Boxes

题意:给出 n 60 n\leq 60 个不同的 60 \leq 60 的数,当 i , j , k i,j,k 满足 a i , a j , a k a_i,a_j,a_k 都未被删去, a i a j a_i | a_j 并且 a i a k a_i | a_k 时可以将 a k a_k 删去,求能删除最多数的删除序列数。

a i a j a_i | a_j 视作 i j i \rightarrow j 的连边,则我们对于每个弱连通图分别计算方案。
(注意下文的讨论中图是弱联通的。)
显然这个如果 i j , j k i\rightarrow j,j\rightarrow k 有边,则 i k i \rightarrow k 有边。
所以如果在某次删除中需要找到一个 i i ,这个 i i 一定可以没有入度。
我们把没有入度的点集看做 S S ,其他点看做 T T
则我们需要求最少删到还有多少点,换个方向考虑,假如一开始我们让一些点存在,然后让这些 i , j , k i,j,k a i , a j a_i,a_j 存在, a k a_k 不存在的拓展出 a k a_k 存在,如果能拓展出所有点,那么这个拓展方案反过来就和合法的删点方案一一对应。
首先 S S 中的点不可能被删,所以 S S 中的点一开始都是存在的,删最多的点意味着 T T 中一开始存在的点要尽量少,最少为 1 1 ,接下来我们给出构造的方案使得 T T 中一开始的点数为 1 1
对于 T T 中存在的点,找能够到达他的所有的 S S 中的点 x x ,那么 x x 能到达的点都会变为存在,再重复这个过程即可,容易发现弱连通图中的所有点都会变为存在,也就是 T T 中任意一个点开始我们都可以让所有点存在。
考虑如何根据这个过程构造出删点方案,我们定义一种新的标记,这种标记只会打在 S S 的点中,对于 T T 中一开始的点,我们找能够到达他的所有的 S S 中的点 x x ,给 x x 打上标记,之后我们找下一个被删除的点 y y ,这个 y y 只需要保证能够到达他的所有的 S S 中的点存在一个 p p p p 是有标记的即可,接下来给能够到达他的所有的 S S 中的点 x x 打上标记,如此重复即可得到一个删点方案。
可以证明 S S 中的点数 60 4 \leq \frac {60}4 ,首先可以认为 S S 中的点都应该 30 \leq 30 ,否则没有出边也没有入边不满足强联通,对于 30 \leq 30 的所有点, S S 中的点构成了一个反链,其大小 1...30 \leq 1...30 的最小链覆盖,我们可以给出一个链覆盖为 { 1 , 2 , 4 , 8.... } , { 3 , 6 , 12 } . . . \{1,2,4,8....\},\{3,6,12\}... 等形如一个奇数 a × 2 k a\times 2^k 30 2 \frac {30}2 条链,所以 S S 中的点数 15 \leq 15
于是我们用一个状压 d p dp f s , i f_{s,i} 表示目前集合 s s 的点打上了标记,已经删去了 i i 个点,
注意到我们不应该把 T T 中的点是否被选过纳入状态,所以我们需要有两种转移。
一种是删去能到达他的只有 s s 中的点,那么第一维不变,第二维 + 1 +1 ,需要预处理出能到达他的只有 s s 中的点数 c c ,通过 c i c-i 统计出还没被删的点数来做决策。
第二种是删去能使 s s 变大的点并且这个点能被 s s 到达,这个点显然不会被删过,所以我们可以有一个 O ( 2 15 n 2 ) O(2^{15} n^2) d p dp
A C   C o d e \mathcal AC \ Code

#include<bits/stdc++.h>
#define maxn 65
#define rep(i,j,k) for(int i=(j),LIM=(k);i<=LIM;i++)
#define per(i,j,k) for(int i=(j),LIM=(k);i>=LIM;i--)
#define LL long long
#define mod 1000000007
#define vi vector<int>
#define pb push_back
using namespace std;

int n,a[maxn];
int F[maxn],in[maxn],C[maxn][maxn],sta[1<<15],f[1<<15],g[1<<15][maxn];
int Find(int u){ return !F[u] ? u : F[u] = Find(F[u]); }
vi G[maxn];

int main(){
	scanf("%d",&n);
	rep(i,1,n) scanf("%d",&a[i]);
	sort(a+1,a+1+n);
	rep(i,1,n) rep(j,i+1,n) if(a[j] % a[i] == 0){
		int x = Find(j) , y = Find(i);
		in[j]++;
		if(x ^ y) F[x] = y;
	}
	rep(i,C[0][0]=1,n) rep(j,C[i][0]=1,i) C[i][j] = (C[i-1][j-1] + C[i-1][j]) % mod;
	rep(i,1,n) G[Find(i)].pb(i);
	int ans = 1 , hd = 0;
	rep(i,1,n) if(G[i].size() > 1){
		vi S;
		for(int v:G[i]) if(!in[v]) S.pb(v);
		int N = 1 << S.size();
		memset(f,0,sizeof f) , memset(g,0,sizeof g);
		int cnt = 0;
		for(int v:G[i]) if(in[v]){
			cnt ++;
			rep(j,0,S.size()-1)
				if(a[v] % a[S[j]] == 0)
					sta[v] |= 1 << j;
			f[sta[v]] ++;
		}
		rep(i,0,S.size()-1) rep(j,0,N-1) if(j >> i & 1)
			f[j] += f[j-(1<<i)];
		g[0][0] = 1;
		rep(j,0,N-1) rep(k,0,cnt) if(g[j][k]){
			if(k < f[j]) g[j][k+1] = (g[j][k+1] + 1ll * g[j][k] * (f[j] - k)) % mod;
			for(int p:G[i]) if(in[p] && ((sta[p] & j) != sta[p]) && ((sta[p] & j) || j == 0)) 
				g[j | sta[p]][k+1] = (g[j|sta[p]][k+1] + g[j][k]) % mod;
		}
		ans = 1ll * ans * g[N-1][cnt] % mod * C[hd + cnt - 1][cnt - 1] % mod; 
		hd += cnt - 1;
	}
	printf("%d\n",ans);
}

HDU 6691 Minimum Spanning Trees

我的某远古题解

猜你喜欢

转载自blog.csdn.net/qq_35950004/article/details/107076855