@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 存储会加速很多。

posted @ 2020-03-17 21:07  Tiw_Air_OAO  阅读(136)  评论(0编辑  收藏  举报