2018雅礼集训 方阵 和 BZOJ4671 异或图
方阵
给定 \(n × m\) 的矩阵,每个格子填上 \([1, c]\) 中的数字,求任意两行、两列均不同的方案数。
\(n, m ≤ 5000\)。
题解
我们默认矩阵为 \(n\) 行的,以下将 \(n\) 视为常数。
设 \(g(m)\) 表示 \(m\) 列的矩阵,满足任意两行不相同的方案数。那么
设 \(f_m(i)\) 表示 \(m\) 列的矩阵,满足任意两行不相同,且列总共分为 \(i\) 类的方案数。那么
时间复杂度 \(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\)个联通块轮廓,在保证内部联通的情况下,外部块与块间无连边。
显然:
根据斯特林反演:
故
而\(\begin{bmatrix}i\\1\end{bmatrix}\)是阶乘形式\(\begin{bmatrix}i\\1\end{bmatrix}=(i-1)!\),化简答案为
考虑如何求出\(f_i\)。
我们用\(\text{Bell}(n)\)的复杂度枚举子集划分,把每个子集作为一个块,两个不同块之间不能连边,块内任意。那么用\(x_1,x_2,\dots,x_s\)表示图的选择情况,可以得出对每条跨块的边的选择情况的异或方程组
对这个方程组进行高斯消元得到秩\(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;
}