题意
定义两个结点数相同的图 G1 与图 G2 的异或为一个新的图 G, 其中如果 (u, v) 在 G1 与G2 中的出现次数之和为 1, 那么边 (u, v) 在 G 中, 否则这条边不在 G 中.现在给定 s 个结点数相同的图 G1…s, 设 S = {G1, G2, … , Gs}, 请问 S 有多少个子集的异或为一个连通图?
2≤n≤10,1≤s≤60.2≤n≤10,1≤s≤60.
分析
发现要直接求连通图的话并不好求,那就考虑容斥。
先用O(贝尔数)的时间来把n个点划分成若干个集合,现在要求两两集合之间没有连边,集合内部可以任意连的方案数。
这个我们可以把每个图看做一个01变量,对于每一条连接两个集合的边,都可以对这s个集合列一个方程。然后对这些方程高斯消元一下求出主元数量tt,那么方案数就是。
设fmfm表示所有m-划分的贡献和,gmgm表示原图中恰好有m个连通块的方案数,不难得到
fm=∑i=mnS(i,m)gifm=∑i=mnS(i,m)gi
斯特林反演一下
gm=∑i=mn(−1)i−ms(i,m)figm=∑i=mn(−1)i−ms(i,m)fi
其中S(i,m)S(i,m)是第二类斯特林数,组合意义是把ii个不同的球放入个相同的盒子内的方案数,s(i,m)s(i,m)是第一类斯特林数,组合意义是把ii个不同的球分成个轮换的方案数。
由于我们只要求g1g1,那么有
g1=∑i=1n(−1)i−1(i−1)!fig1=∑i=1n(−1)i−1(i−1)!fi
只要通过之前的方法把所有的fifi求出来就好了。
代码
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long LL;
int n,m,bel[15];
LL bin[70],G[70],bas[70],f[15];
char str[70];
void gauss(int tot)
{
for (int i=0;i<m;i++) bas[i]=0;
int tim=-1,cnt=0;
for (int x=1;x<n;x++)
for (int y=x+1;y<=n;y++)
{
tim++;
if (bel[x]==bel[y]) continue;
LL s=0;
for (int i=0;i<m;i++) s+=bin[i]*((G[i]&bin[tim])>0);
for (int i=m-1;i>=0;i--)
if (s&bin[i])
{
if (bas[i]) s^=bas[i];
else {cnt++;bas[i]=s;break;}
}
}
f[tot]+=bin[m-cnt];
}
void dfs(int x,int y)
{
if (x>n) {gauss(y);return;}
for (int i=1;i<=y+1;i++)
bel[x]=i,dfs(x+1,max(y,i));
}
int main()
{
scanf("%d",&m);
bin[0]=1;
for (int i=1;i<=62;i++) bin[i]=bin[i-1]*2;
for (int i=0;i<m;i++)
{
scanf("%s",str);int len=strlen(str);
for (int j=0;j<len;j++) G[i]+=str[j]=='1'?bin[j]:0;
}
int len=strlen(str);
for (;n*(n-1)/2<len;n++);
dfs(1,0);
LL ans=0;
for (int i=1,jc=1;i<=n;i++,jc*=i)
if (i&1) ans+=(LL)jc/i*f[i];
else ans-=(LL)jc/i*f[i];
printf("%lld",ans);
return 0;
}