2018雅礼集训 方阵 和 BZOJ4671 异或图

方阵

给定 \(n × m\) 的矩阵,每个格子填上 \([1, c]\) 中的数字,求任意两行、两列均不同的方案数。

\(n, m ≤ 5000\)

题解

我们默认矩阵为 \(n\) 行的,以下将 \(n\) 视为常数。

\(g(m)\) 表示 \(m\) 列的矩阵,满足任意两行不相同的方案数。那么

\[g(m)=(c^m)^\underline{n} \]

\(f_m(i)\) 表示 \(m\) 列的矩阵,满足任意两行不相同,且列总共分为 \(i\) 类的方案数。那么

\[g(m)=\sum_{i=0}^m\begin{Bmatrix}m\\i\end{Bmatrix}f_m(i)\\ f_m(m)=\sum_{i=0}^m(-1)^{m-i}\begin{bmatrix}m\\i\end{bmatrix}g(i)\\ =\sum_{i=0}^m(-1)^{m-i}\begin{bmatrix}m\\i\end{bmatrix}(c^i)^\underline{n} \]

时间复杂度 \(O(nm)\)

CO int N=5000+10;
int S[N][N],P[N];

void real_main(){
	int n=read<int>(),m=read<int>(),c=read<int>();
	P[0]=1;
	for(int i=1;i<=m;++i) P[i]=mul(P[i-1],c);
	int ans=0;
	for(int i=1;i<=m;++i){
		int sum=S[m][i];
		for(int j=0;j<n;++j) cmul(sum,P[i]-j);
		cadd(ans,(m-i)&1?mod-sum:sum);
	}
	printf("%d\n",ans);
}
int main(){
	S[0][0]=1;
	for(int i=1;i<N;++i)for(int j=1;j<=i;++j)
		S[i][j]=add(S[i-1][j-1],mul(i-1,S[i-1][j]));
	for(int T=read<int>();T--;) real_main();
	return 0;
}

BZOJ4671 异或图

定义两个结点数相同的图 \(G_1\) 与图 \(G_2\) 的异或为一个新的图 \(G\), 其中如果 \((u, v)\)\(G_1\)\(G_2\) 中的出现次数之和为 \(1\), 那么边 \((u, v)\)\(G\) 中, 否则这条边不在 \(G\) 中.

现在给定 \(s\) 个结点数相同的图 \(G_{1\dots s}\), 设 \(S = \{G_1, G_2, \dots , G_s\}\), 请问 \(S\) 有多少个子集的异或为一个连通图?

\(2 ≤ n ≤ 10,1 ≤ s ≤ 60.\)

题解

连通性计数相关的问题一般要用到容斥原理,这是因为“连通”非常难处理,因为整体连通并不知道每条边的存在情况,而“不连通”则是可以确定没有任何边相连;而容斥就是用 连通方案=总方案?不连通方案连通方案=总方案?不连通方案,从而将连通计数问题转化为不连通计数的问题。

\(f_i\)表示至少有\(i\)个联通块的方案,形如设立\(i\)个联通块轮廓,联通块内连边随意,联通块与联通块之间无连边。

\(g_i\)表示恰好有\(i\)个联通块的方案,形如设立\(i\)个联通块轮廓,在保证内部联通的情况下,外部块与块间无连边。

显然:

\[f_x=\sum\limits_{i=x}^n\begin{Bmatrix}i\\x\end{Bmatrix}g_i \]

根据斯特林反演:

\[g_x=\sum\limits_{i=x}^n (-1)^{i-x}\begin{bmatrix}i\\x\end{bmatrix}f_i \]

\[g_1=\sum\limits_{i=1}^n (-1)^{i-1}\begin{bmatrix}i\\1\end{bmatrix}f_i \]

\(\begin{bmatrix}i\\1\end{bmatrix}\)是阶乘形式\(\begin{bmatrix}i\\1\end{bmatrix}=(i-1)!\),化简答案为

\[g_1=\sum\limits_{i=1}^n (-1)^{i-1}(i-1)!f_i \]

考虑如何求出\(f_i\)

我们用\(\text{Bell}(n)\)的复杂度枚举子集划分,把每个子集作为一个块,两个不同块之间不能连边,块内任意。那么用\(x_1,x_2,\dots,x_s\)表示图的选择情况,可以得出对每条跨块的边的选择情况的异或方程组

\[x_1[e\in G_1]\ \text{xor}\ x_2[e\in G_2]\ \text{xor}\ \dots\ \text{xor}\ x_s[e\in G_s]=0 \]

对这个方程组进行高斯消元得到秩\(c\),那么自由元的数量就是\(s-c\)。所以\(ans=2^{s-c}\)

需要将跨越块的边单独拿出来重新编号,可以利用线性基来实现快速消元。

co int maxs=65,maxn=15,maxe=50;
char str[maxe];
int s,n,gr[maxs][maxn][maxn];

int ans,A[maxe],fac[maxn];
int bl[maxn];
void solve(int cur,int m){
	if(cur>=n){
		memset(A,0,sizeof A);
		for(int i=0;i<n;++i)
			for(int j=i+1;j<n;++j)if(bl[i]!=bl[j]){
				int tmp=0;
				for(int g=0;g<s;++g) tmp|=gr[g][i][j]<<g;
				for(int k=maxe-1;k>=0;--k)if(tmp>>k&1){
					if(A[k]) tmp^=A[k];
					else {A[k]=tmp;break;}
				}
			}
		int c=0;
		for(int k=0;k<maxe;++k) c+=A[k]>0;
		ans+=(m&1?1:-1)*(1LL<<(s-c))*fac[m-1];
		return;
	}
	for(int i=1;i<=m+1;++i)
		bl[cur]=i,solve(cur+1,max(m,i));
}

signed main(){
	read(s);
	for(int i=0;i<s;++i){
		scanf("%s",str);
		int l=strlen(str),t=0;
		n=(1+sqrt(1+(l<<3)))/2;
		for(int x=0;x<n;++x)
			for(int y=x+1;y<n;++y)
				gr[i][x][y]=str[t++]-'0';
	}
	fac[0]=1;
	for(int i=1;i<=n;++i) fac[i]=fac[i-1]*i;
	solve(0,0);
	printf("%lld\n",ans);
	return 0;
}

posted on 2020-01-04 16:41  autoint  阅读(300)  评论(0编辑  收藏  举报

导航