@bzoj - 4671@ 异或图
@description@
定义两个结点数相同的图 G1 与图 G2 的异或为一个新的图 G, 其中如果 (u, v) 在 G1 与 G2 中的出现次数之和为 1, 那么边 (u, v) 在 G 中, 否则这条边不在 G 中.
现在给定 s 个结点数相同的图 G1...s, 设 S = {G1, G2, . . . , Gs}, 请问 S 有多少个子集的异或为一个连通图?
@solution@
记 f[i] 表示得到恰好 i 个连通块的方案数,再记 g[i] 表示至少 i 个连通块的方案数。则:
\[g[i] = \sum_{j=i}^{n}{j\brace i}f[j]
\]
这是很显然的。然后我们反演一下:
\[f[i] = \sum_{j=i}^{n}(-1)^{j-i}{j\brack i}g[j]
\]
最后要求连通,所以实际上求 f[1] 的值。然后我们又知道 \({n\brack 1} = (n - 1)!\),所以:
\[ans = f[1] = \sum_{i=1}^{n}(-1)^{i-1}(i-1)!g[i]
\]
考虑怎么求 g。我们可以搜索 n 个点哪些点可能在同一个块内(即将 n 个球染色),搜索量为第 n 个贝尔数。
提出不可能在同一块内的点对之间的边,把 s 个图在这些边上作线性基。然后是经典的线性基计数。
@accepted code@
#include <bitset>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
int s, n, m;
bool tag[60]; ull b[60]; int siz;
void insert(ull x) {
for(int i=m-1;i>=0;i--) {
if( !tag[m-i-1] || !((x >> i) & 1) ) continue;
if( b[i] == 0 ) {
b[i] = x, siz++;
return ;
}
else x ^= b[i];
}
}
int clr[10]; ll pw2[65]; ull a[60];
ll solve() {
int p = 0; siz = 0;
for(int i=0;i<m;i++) b[i] = 0;
for(int i=0;i<n;i++)
for(int j=i+1;j<n;j++)
tag[p++] = (clr[i] != clr[j]);
for(int i=0;i<s;i++)
insert(a[i]);
return pw2[s - siz];
}
ll ans[15];
void dfs(int d, int l) {
if( d == n ) {
ans[l + 1] += solve();
return ;
}
dfs(d + 1, clr[d] = l + 1);
for(int i=0;i<=l;i++)
clr[d] = i, dfs(d + 1, l);
}
char str[60];
int main() {
pw2[0] = 1; for(int i=1;i<=60;i++) pw2[i] = 2*pw2[i-1];
scanf("%d", &s);
for(int i=0;i<s;i++) {
scanf("%s", str), m = strlen(str);
for(int j=0;j<m;j++)
a[i] = (a[i] << 1 | (str[j] - '0'));
}
for(int i=2;i<=10;i++)
if( i*(i - 1)/2 == m ) n = i;
dfs(0, -1); ll res = 0, p = 1;
for(int i=1;i<=n;p*=i,i++)
res = res + (i & 1 ? ans[i]*p : -ans[i]*p);
printf("%lld\n", res);
}
@details@
用 unsigned long long 存储会加速很多。