题解

二项式反演

对于一个长度为 \(n\) 的序列 \(a\),满足 \(0\le a_i\le 1\),且要表示 \(\left[\displaystyle \sum_{i=1}^n a_i =k\right]\)。

则可以看作 \(\displaystyle [x^k]\prod_{i=1}^n x^{a_i}\)。在 \(0\le a_i\le 1\) 时,\(x^{a_i}=(x-1)a_i+1\)。

也就是 \(\displaystyle [x^k]\prod_{i=1}^n \left((x-1)a_i+1\right)\)。

乘法分配律:\(\displaystyle [x^k]\sum_{S\subseteq[n]} \prod_{i\in S}(x-1)a_i=\sum_{S\subseteq[n]} [x^k](x-1)^{|S|}\prod_{i\in S}a_i\)。

容易发现 \(\displaystyle [x^k](x-1)^{|S|}=\binom{|S|}{k}(-1)^{|S|-k}\)。

莫比乌斯反演

设 \(n=p_1^{\alpha_1}p_2^{\alpha_2}\dots p_l^{\alpha_l}\)。把 \(f_n\) 记在 \(x_1^{\alpha_1}x_2^{\alpha_2}\dots x_l^{\alpha_l}\)。

倍数关系可以记为 \(\displaystyle \prod_{i=1}^{\infty} \frac{1}{1-x_i}\),反演就是求逆,\(\displaystyle \prod_{i=1}^l (1-x_i)\)。

和莫比乌斯函数的表达式是一样的。

霞む夏の灯

题目链接。

\[\begin{align} \prod_{i=1}^n c_{\min(|i-p_i|,m)}\\ =\prod_{i=1}^n \left([|i-p_i|<m]c_{|i-p_i|}+[|i-p_i|\ge m]c_{m}\right)\\ =\prod_{i=1}^n \left([|i-p_i|<m]c_{|i-p_i|}+(1-[|i-p_i|<m])c_{m}\right)\\ =\prod_{i=1}^n \left(c_m+[|i-p_i|<m](c_{|i-p_i|}-c_m)\right) \end{align} \]

钦定 \(i\) 选择前一项还是后一项。只需要直接状压前后共 \(2m-1\) 个位置的选择状态并 dp 即可。不需要其他题解里说的大分讨。

The Best Problem of 2021

题目描述。

:::info[问题泛化]

定义最简基是一个线性基,满足其主元列只有恰好一个 \(1\)。容易发现一个线性空间对应唯一一个最简基。

对 \(B\) 高斯消元,得到一个最简基。最简基有另一个性质是:

首先满足 \(B_1>B_2>B_3>\dots>B_n\)。定义 \(\mathrm{hi}(x)\) 为 \(x\) 的最高位,那么 \(\mathrm{hi}(B_1)>\mathrm{hi}(B_2)>\mathrm{hi}(B_3)>\dots>\mathrm{hi}(B_n)\)。每个数的最高位就是主元位。

那么我们设计一个二进制数 \(f(x)\),如果这个数第 \(i\) 位是 \(1\),那么 \(x\) 就要异或上 \(B_{n-i}\)。这样 \(x\) 在这个线性空间中的排名就是 \(C+1\)。

那么对于原题里的 \(X\),我们可以二分找到一个对应的 \(X'\),满足所有 \(f(x)\le X'\) 的数,\(x\le X\)。

:::

:::info[容斥或反演]

问题现在变成了 \([1,X]\) 中有多少个满秩的集合。也就是秩为 \(n\)。

可以算的是一个秩为 \(i\) 的最简基去张成集合 \(S\),然后在 \(S\cap[1,X]\) 中任选一个子集。设这个答案为 \(g_i\)。

现在一个集合的实际秩为 \(c\),那么其会被 \(g_c,g_{c+1},g_{c+2},\dots,g_{n}\) 算到。

记实际秩为 \(i\) 的集合被 \(g_j\) 算到的次数为 \(C(i,j)\)。

现在我们设一个容斥系数 \(\mathcal{H}_i\),最后答案是 \(g_i\mathcal{H_i}\) 的和。

那么我们可以得到 \(\mathcal{H}_n=1\)。接下来考虑算 \(\mathcal{H}_{n-1}\),那么就要减去 \(C(n-1,n)\mathcal{H}_n\)。

以此类推可以得到暴力反演式:

\[\mathcal{H}_j=-\sum_{k=j+1}^n C(j,k)\mathcal{H}_k \]

验证一下,此时一个实际秩为 \(c\) 的集合会被贡献 \(C(c,c)\mathcal{H}_c+C(c,c+1)\mathcal{H}_{c+1}+\dots+C(c,n)\mathcal{H}_n\)。根据定义这样会被算恰好 \(0\) 次。

接下来补充一下细节。

:::

:::info[细节]

比如 \(C(i,j)\) 到底是什么?我们给秩为 \(i\) 的集合 \(S\) 消元,那么其可以对应一个秩为 \(j\) 的最简基吗?如果我们记 \(f(i,j)\) 为 \([0,2^{i})\) 中秩为 \(j\) 的最简基数量,那么 \(C(i,j)=f(n-i,j-i)\)。证明可以考虑往剩下的位里面塞一个最简基,然后如果冲突了就消元,这样可以构成单射,另外一个方向的单射也是类似的。

考虑怎么算 \(f(i,j)\)。有递推式 \(f(i,j)=f(i-1,j)+f(i-1,j-1)2^{i-j}\)。这其实是 \(q\)-二项式系数,但是我不懂,就不管了。边界条件是 \(f(i,0)=1\)。

然后是 \(g_i\) 怎么求?

如果 \(X\) 够大,那就没有这个烦恼,直接套用 \(f(n,i)2^{2^i}\) 就好了。这提示我们 dp。

先想想怎么求这个基中 \(X\) 的 \(\mathrm{rank}\)。答案就是 \(2^{\mathrm{rank}(X)}\)。

考虑贪心,从高往低看最简基,有四种情况:

  • 选不选都比 \(X\) 小。
  • 不选一定比 \(X\) 小,选了不一定。
  • 不选一定比 \(X\) 小,选了一定不。
  • 不选不一定比 \(X\) 小,选了一定不。

听起来有点绕,其实就是从高到低比较每一位。

那么我们的答案是怎么计算的呢?显然到第一,三种情况就可以停了。又比如我们选了最高的数,那么其四种情况:

  • 到次高的主元位为止,最高数都与 \(X\) 一模一样。
  • 到次高的主元位为止,\(X\) 一直都是 \(0\)。
  • 到次高的主元位为止,最高数比 \(X\) 小了。
  • 到次高的主元位为止,最高数比 \(X\) 大了。

第三,四种情况,直接找一个比 \(X\) 小或大的最高数,然后后面就是算类似 \(f\) 的东西。

第一种情况,不选最高数的贡献可以直接乘上了,然后就是继续 dp。后面最高数因为一直被选,可以起到一个干扰的作用,说的很迷惑,其实就还是乘上最高数乱选的贡献。

第二种情况,最高数可以直接扔掉了,也是乱选,然后就是继续 dp。总之每次 dp 到一处时,前面的主元就是可以凑出 \(X\) 的。

:::

:::info[另一些细节]

但是上面这个 dp 过程要从高往低 dp,还要记之后选了几个之前选了几个,我们再整理一下。

比如我们不妨钦定最高位 \(n\) 一定被选,显然如果 \(X\) 不选这一位那么总体的答案就直接是 \(0\) 了。这样做的目的是保护每一个非主元位,出现 \(0\) 和出现 \(1\) 的概率是一致的。

那么我们做一个转化,我们可以去钦定非主元位是一个数 \(0/1\),这样和实际方案冲突的概率只有 \(\dfrac{1}{2}\),合起来就是 \(\dfrac{1}{2^{n-i}}\),最后乘上就行。

我们从低往高 dp,求 \(g(i,j)\) 表示低 \(i\) 位选了 \(j\) 个基,最后在低 \(i\) 位可以 \(\le X\) 的方案数。

这个定义有些抽象,但是应该可以理解。毕竟前面那些位已经 \(=X\) 了。

考虑 \(g(i,j)\) 转移 \(X\) 这一位是 \(0\):

  • \(g(i-1,j-1)2^{i-j}\)。这一位是主元,而且没什么用,不会选的。
  • \(g(i-1,j)\)。这一位不是主元,钦定选择 \(0\) 才能继续。
  • \(f(i-1,j)2^{i-j-1}\)。这一位不是主元,但是钦定选择 \(1\) 也可以结束。

考虑 \(g(i,j)\) 转移 \(X\) 这一位是 \(1\):

  • \(g(i-1,j-1)2^{i-j}2^{2^{j-1}}\)。这一位是主元,而且必须选。如果不选,那么会留下 \(2^{j-1}\) 个数可选可不选。
  • \(g(i-1,j)\)。这一位不是主元,钦定这一位是 \(1\)。
  • \(f(i-1,j)2^{2^j}2^{i-j-1}\)。这一位不是主元,钦定这一位是 \(0\)。

:::

:::info[实现]

#include <bits/stdc++.h>
using namespace std;

const int N=2007;
int n,m;
int vi[N];
bitset<N> b,c,d,p[N];
void work(){
	for(int j=m-1;j>=0;j--){
		if(b[j]){
			if(vi[j]){
				b^=p[j];
			}else{
				vi[j]=1;
				p[j]=b;
				return;
			}
		}
	}
	cout<<"0"<<endl;
	exit(0);
}
void chk(){
	for(int j=0;j<m;j++){
		if(!vi[j]) continue;
		for(int k=j+1;k<m;k++){
			if(!vi[k]) continue;
			if(p[k][j]) p[k]^=p[j];
		}
	}
}
void del(){
	for(int t=0;t<n;t++) if(d[t]==1){
		d[t]=0;
		for(int k=0;k<t;k++) d[k]=1;
		return;
	}
	cout<<"0"<<endl;
	exit(0);
}
const int mod=998244353;
int f[N][N],g[N][N],pw[N],pwpw[N];
int G[N],H[N];
int qpow(int a,int b,int mod){
	int r=1;
	while(b){
		if(b&1) r=1ll*r*a%mod;
		a=1ll*a*a%mod;
		b=b>>1;
	}
	return r;
}
void solve1(){
	for(int i=0;i<=n;i++) pw[i]=qpow(2,i,mod);
	for(int i=0;i<=n;i++) pwpw[i]=qpow(2,qpow(2,i,mod-1),mod);
	f[0][0]=1;
	g[0][0]=2;
	for(int i=1;i<=n;i++){
		f[i][0]=1;
		if(d[i-1]==0) g[i][0]=(g[i-1][0]+1ll*f[i-1][0]*pw[i-1])%mod;
		if(d[i-1]==1) g[i][0]=(g[i-1][0]+1ll*f[i-1][0]*pw[i])%mod;
		for(int j=1;j<=i;j++){
			f[i][j]=(f[i-1][j]+1ll*f[i-1][j-1]*pw[i-j])%mod;
			g[i][j]=g[i-1][j];
			if(d[i-1]==0){
				g[i][j]=(g[i][j]+1ll*g[i-1][j-1]*pw[i-j])%mod;
				if(j<i) g[i][j]=(g[i][j]+1ll*f[i-1][j]*pw[i-j-1])%mod;
			}
			if(d[i-1]==1){
				g[i][j]=(g[i][j]+1ll*g[i-1][j-1]*pw[i-j]%mod*pwpw[j-1])%mod;
				if(j<i) g[i][j]=(g[i][j]+1ll*f[i-1][j]*pw[i-j-1]%mod*pwpw[j])%mod;
			}
		}
	}
	for(int i=0;i<n;i++) G[i+1]=(G[i+1]+1ll*g[n-1][i]*pwpw[i])%mod;
	for(int i=0;i<n;i++) G[i]=(G[i]+1ll*f[n-1][i]*pwpw[i])%mod;
}
void solve2(){
	H[n]=1;
	for(int i=n-1;i>=0;i--){
		for(int j=i+1;j<=n;j++){
			H[i]=(H[i]-1ll*f[n-i][j-i]*H[j]%mod+mod)%mod;
		}
	}
	int ans=0;
	for(int i=0;i<=n;i++) ans=(ans+1ll*G[i]*H[i])%mod;
	ans=1ll*ans*qpow(2,mod-2,mod)%mod;
	cout<<ans<<endl;
}
signed main(){
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	cin>>n>>m;
	char ch;
	for(int i=1;i<=n;i++){
		for(int j=0;j<m;j++){
			cin>>ch;
			b[m-j-1]=ch-'0';
		}
		work();
	}
	for(int j=0;j<m;j++){
		cin>>ch;
		b[m-j-1]=ch-'0';
	}
	chk();
	int cnt=0,f=0;
	for(int j=m-1;j>=0;j--){
		if(vi[j]) cnt++;
		if(c[j]==b[j]) continue;
		if(vi[j]){
			c^=p[j],d[n-cnt]=1;
		}else{
			if(c[j]){
				f=1;
				break;
			}else{
				f=0;
				for(int j=cnt+1;j<=n;j++) d[n-j]=1;
				break;
			}
		}
	}
	if(f) del();
	if(d[n-1]==0){
		cout<<"0"<<endl;
		exit(0);
	}
	solve1();
	solve2();
	return 0;
}

:::

posted @ 2026-10-22 19:10  ty_mxzhn  阅读(44)  评论(1)    收藏  举报