方阵
给定 (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
",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_{1dots s}), 设 (S = {G_1, G_2, dots , G_s}), 请问 (S) 有多少个子集的异或为一个连通图?
(2 ≤ n ≤ 10,1 ≤ s ≤ 60.)
题解
连通性计数相关的问题一般要用到容斥原理,这是因为“连通”非常难处理,因为整体连通并不知道每条边的存在情况,而“不连通”则是可以确定没有任何边相连;而容斥就是用 连通方案=总方案?不连通方案连通方案=总方案?不连通方案,从而将连通计数问题转化为不连通计数的问题。
(f_i)表示至少有(i)个联通块的方案,形如设立(i)个联通块轮廓,联通块内连边随意,联通块与联通块之间无连边。
(g_i)表示恰好有(i)个联通块的方案,形如设立(i)个联通块轮廓,在保证内部联通的情况下,外部块与块间无连边。
显然:
根据斯特林反演:
故
而(egin{bmatrix}i\1end{bmatrix})是阶乘形式(egin{bmatrix}i\1end{bmatrix}=(i-1)!),化简答案为
考虑如何求出(f_i)。
我们用( ext{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
",ans);
return 0;
}