生成函数、组合数学

[ARC089D] ColoringBalls

咕着
咕着

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

#include
#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
#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

生成函数、组合数学_第1张图片
容斥,枚举有 k k k种元素用了 ≤ 1 \leq 1 1次,其中有 j j j种元素用了 1 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} k=0n(1)k(kn)22nkj=0k(jk)p=0j{jp}2(nk)p
注意 2 2 n − k 2^{2^{n-k}} 22nk ( 2 2 ) n − k (2^2)^{n-k} (22)nk是完全不同的。
注意到有恒等式 ∑ 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} j=pk(jk){jp}={k+1p+1}
证明:考虑组合意义,相当于是拿 k − j k-j kj个数不选,剩下的 j j j个数分到 p p p组内,如果我们加一个组来装着 k − j k-j kj个数,那么这个组可能为空,同时发现这个组和别的组不一样,但是第二类斯特林数组之间是没有标号区别的,所以我们就往空组加入一个 0 0 0(强行规定 0 0 0所在的组为不选组),则非空和有区别这两个限制都被满足了,方案数就是 { k + 1 p + 1 } \begin{Bmatrix} k+1\\p+1 \end{Bmatrix} {k+1p+1}
但是其实这样做很傻,直接类似第二类斯特林数 d p dp 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} gi,j=gi1,j1+(j+1)gi1,j
后面那个 j + 1 j+1 j+1中的 + 1 +1 +1就代表了不选这一选项,直接 d p dp 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} ans=k=0n(1)k(kn)22nkp=0k{k+1p+1}2(nk)p

A C   C o d e \mathcal AC \ Code AC Code

#include
#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 ai为在 [ L i , R i ] [L_i,R_i] [Li,Ri]之间均匀随机的离散变量,求 a i a_i ai不增的概率。

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

A C   C o d e \mathcal AC \ Code AC Code

#include
#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) (x,y),求走 n n n步(每步可以让横坐标或纵坐标 ± 1 \pm 1 ±1)的方案数使得途中不经过距离原点曼哈顿距离为 D ≤ 4 D \leq 4 D4的点。

容斥,设 f i , j , k f_{i,j,k} fi,j,k表示走了 i i i步之后到达了 j j j号禁止点(禁止点为距离原点曼哈顿距离为 D D D的点集),至少一共走过了 k 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} 4n+(1)kfi,j,k4ni
注意到我们直接计算容斥系数可以不需要 k k k这一维。
所以 f i , j f_{i,j} fi,j在我们可以 O ( 1 ) O(1) O(1)直接计算两个点之间走 p p p步到达的方案数后,
复杂度就是 O ( n 2 D 2 ) O(n^2D^2) O(n2D2)的。
设一个点在 ( a , b ) (a,b) (a,b)另一个点在 ( c , d ) (c,d) (c,d),一共走 p p p步要到达。
发现因为有相反的方向(减少的方向)所以计数很困难,考虑如何用上一共走 p p p步的条件。

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

A C   C o d e \mathcal AC \ Code AC Code

#include
#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 Ai概率选 i i i来异或你手上的数,问第一次得到 1.... 2 n − 1 1....2^n-1 1....2n1的期望时间。

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

A C   C o d e \mathcal AC \ Code AC Code

#include
#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 A1...AK,求 a 1 + a 2 . . . + a n = S a_1+a_2...+a_n = S a1+a2...+an=S的方案数   m o d   2 \bmod 2 mod2,其中 a i a_i ai A 1 . . . A k A_1...A_k A1...Ak中的一个, 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 K200Ai1e5,n1e18,S1e18

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

A C   C o d e \mathcal AC \ Code AC Code

#include
#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 Klog2n+1 1... n 1...n 1...n的排列的映射 p i ( x ) p_i(x) pi(x),对于所有 x , y x,y x,y都存在 q K ( q K − 1 ( . . . q 1 ( x ) ) ) = y q_K(q_{K-1}(...q_1(x))) = y qK(qK1(...q1(x)))=y,其中 q i ( x ) = p i ( x ) q_i(x) = p_i(x) qi(x)=pi(x) p i ( x ) − 1 p_i(x)^{-1} pi(x)1

P = ⌊ log ⁡ 2 n ⌋ P = \lfloor \log_2n \rfloor P=log2n
则我们让 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] pi,j=j+2imodn,i[0,P]
这样可以组合出来 n n n以内所有奇数,包括负的。
证明可以考虑归纳证明。
如果 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\} {2,3,0,1,6,7,4,5....n2,n1,n4,n3}来让我们可以使得一个位置移动奇数或偶数,注意这时这个排列是可以让 x , y x,y x,y的奇偶性不同的,所以 2 P 2^{P} 2P这个排列就不需要了(刚好卡进),我们只需要找距离最近也就是 < 2 P \lt 2^P <2P的那边走。
如果 n n n   m o d   4 = 2 \bmod 4 = 2 mod4=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\} {2,3,0,1,6,7,4,5....n4,n3,n6,n5,n2,n1} { 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\} {0,1,2,3,....n6,n5,n2,n1,n3,n4}即可。
A C   C o d e \mathcal AC \ Code AC Code

#include
#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 n60个不同的 ≤ 60 \leq 60 60的数,当 i , j , k i,j,k i,j,k满足 a i , a j , a k a_i,a_j,a_k ai,aj,ak都未被删去, a i ∣ a j a_i | a_j aiaj并且 a i ∣ a k a_i | a_k aiak时可以将 a k a_k ak删去,求能删除最多数的删除序列数。

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

#include
#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
#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

我的某远古题解

你可能感兴趣的:(生成函数、组合数学)