异或图[bzoj4671]

题目链接

darkbzoj

题意

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

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

$n\leq 10,s\leq 60$。

题解

设 $f_i$ 表示恰好有 $i$ 个连通块的方案数。

这个显然比较难求,那么再设一个 $g_i$ 表示至少有 $i$ 个连通块的方案数。

考虑用 $f_j$ 表示 $g_i$,系数就是将 $j$ 个不同的连通块放到 $i$ 个相同的盒子里方案数,即第二类斯特林数 $S_2(j,i)$。

那么有:$g_i=\sum_{j=i}^nS_2(j,i)f_j$。

斯特林反演一下得到:$f_i=\sum_{j=i}^nS_1(j,i)(-1)^{j-i}g_j$。

我们需要求的是 $f_1=\sum_{i=1}^n(i-1)!(-1)^{i-1}g_i$。

因此题目可转换成求 $g_i$。

由于点数比较少,我们可以枚举所有的集合划分,每个集合内的点可以随便连,不同集合之间的点不存在连边。

然后我们只考虑不同集合之间的边,对于每个图,是否存在这些边可以写成一个二进制数,需要在 $s$ 个数中选一些,使得异或起来为 $0$,这个用线性基统计一下即可。

程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <vector>
#include <cstring>
#include <queue>
using namespace std;
#define FO(x) freopen(#x".in","r",stdin),freopen(#x".out","w",stdout)
#define fo(i,j,k) for(int i=(j),end_i=(k);i<=end_i;i++)
#define ff(i,j,k) for(int i=(j),end_i=(k);i< end_i;i++)
#define fd(i,j,k) for(int i=(j),end_i=(k);i>=end_i;i--)
#define DEBUG(x) cerr<<#x<<"="<<x<<endl
#define all(x) (x).begin(),(x).end()
#define cle(x) memset(x,0,sizeof(x))
#define lowbit(x) ((x)&-(x))
#define ll long long
#define ull unsigned ll
#define db double
#define lb long db
#define pb push_back
#define mp make_pair
#define fi first
#define se second
inline int read()
{
int x=0; char ch=getchar(); bool f=0;
for(;ch<'0'||ch>'9';ch=getchar()) if(ch=='-') f=1;
for(;ch>='0'&&ch<='9';ch=getchar()) x=(x<<3)+(x<<1)+(ch^48);
return f?-x:x;
}
int n,m,a[12];
bool g[62][12][12];
ll ans,fac[12],base[64];
inline void calc(int siz)
{
memset(base,0,sizeof(base));
int cnt,tot=0; ll sum;
ff(k,0,m)
{
cnt=sum=0;
ff(i,0,n)
ff(j,i+1,n)
if(a[i]!=a[j])
sum|=((ll)g[k][i][j]<<cnt),cnt++;
fd(i,cnt-1,0)
if((sum>>i)&1)
{
if(!base[i]) {tot++,base[i]=sum; break;}
else sum^=base[i];
}
}
if(siz&1) ans+=(1ll<<(m-tot))*fac[siz-1];
else ans-=(1ll<<(m-tot))*fac[siz-1];
}
void dfs(int u,int k)
{
if(u>=n) return calc(k);
fo(i,1,k+1) a[u]=i,dfs(u+1,max(i,k));
}
char s[100];
int main()
{
m=read();
ff(k,0,m)
{
scanf("%s",s); int t=0;
if(!n) n=(1+sqrt(1+8*strlen(s)))/2;
ff(i,0,n)
ff(j,i+1,n)
g[k][i][j]=(s[t++]-'0');
}
fac[0]=1;
fo(i,1,n) fac[i]=fac[i-1]*i;
dfs(0,0);
printf("%lld",ans);
return 0;
}