BZOJ4671 异或图(斯特林反演+贝尔数+线性基)

题目:BZOJ4671

题目描述:

\(s\)张含有\(n\)个点的图,选出两张图\(G_i\)\(G_j\),将它们异或起来,就会得到一张新图\(G'\),对于新图上的一条边\((u,v)\),它存在当且仅当这条边存在于\(G_i\)\(G_j\)中的恰好一个图上

求这\(s\)张图中有多少个子集,使得这个子集中的所有图异或起来是一张连通图

\(s \leq 60\)\(n \leq 10\)

蒟蒻题解:

发现正着推很难推,考虑容斥

最后的图是一张联通图,说明最后图中只存在一个连通块

设图中存在恰好\(i\)个连通块的方案数为\(f_i\),存在至少\(i\)个连通块的方案数为\(g_i\),那么便有:

\[g_i = \sum_{k=i}^n \begin{Bmatrix}k\\i\end{Bmatrix} f_k \]

根据斯特林反演,我们可以得到:

\[f_i = \sum_{k=i}^n (-1)^{k-i} \begin{bmatrix}k\\i\end{bmatrix} g_k \]

则最终答案:

\[Ans = f_1 = \sum_{k=1}^n (-1)^{k-1} \begin{bmatrix}k\\1\end{bmatrix} g_k = \sum_{k=1}^n (-1)^{k-1} (k-1)! g_k \]

那么现在的问题就是如何求\(g_k\)

我们暴力枚举最后联通块的情况,复杂度是\(\mathcal O(B_n)\)

对于连通块的情况已知,任意一个连通块内的边是可以任意连的(这里的连通块不是真正意义上的连通块,而是至少为一个连通块,它可能可以拆分成若干个连通块),对于任意两个连通块之间不能有边相连

对于一个图,它如果连接了多个连通块,那么便把它压进线性基里面,如果它能放进去,那么便放进去,且不能选择这个图,如果它不能放进去,说明它和已放进去的图中的某一些可以异或起来,使得它不连接任意两个连通块,那么选择这个图的时候也要把那些图给异或进来,所以方案数为\(2^{n-t}\),其中\(t\)为线性基里面有值的位置数

时间复杂度为\(\mathcal O(B_nsn^2)\)

参考程序:

#include<bits/stdc++.h>
using namespace std;
#define Re register int
typedef long long ll;

const int N = 65;
int n, m, a[N];
ll ans, f[N], g[N * N];
bool G[N][N][N];
char s[N];

inline void dfs(int x, int y)
{
	if (x == m)
	{
		int u = 0, w = 0;
		for (Re i = 0; i < m - 1; ++i)
			for (Re j = i + 1; j < m; ++j)
				if (a[i] ^ a[j]) g[u++] = 0;
		for (Re i = 0; i < n; ++i)
		{
			ll v = 0;
			for (Re j = 0; j < m - 1; ++j)
				for (Re k = j + 1; k < m; ++k)
					if (a[j] ^ a[k]) v = (v << 1ll) | G[i][j][k];
			if (!v) continue;
			for (Re j = 0; j < u; ++j)
			{
				if (!((v >> j) & 1)) continue;
				if (!g[j])
				{
					g[j] = v, ++w;
					break;
				}
				v ^= g[j];
			}
		}
		f[y + 1] += 1ll << (n - w);
		return;
	}
	for (Re i = 0; i <= y + 1; ++i) a[x] = i, dfs(x + 1, i > y ? i : y);
}

int main()
{
	scanf("%d", &n);
	for (Re i = 0; i < n; ++i)
	{
		scanf("%s", s);
		if (!i)
		{
			int len = strlen(s);
			while (m * (m - 1) / 2 < len) ++m;
		}
		int u = 0;
		for (Re j = 0; j < m - 1; ++j)
			for (Re k = j + 1; k < m; ++k) G[i][j][k] = s[u++] ^ 48;
	}
	dfs(1, 0);
	for (Re i = 1, j = 1, k = 1; i <= m; j = -j, k *= i, ++i) ans += f[i] * j * k;
	printf("%lld", ans);
	return 0;
}
posted @ 2021-06-02 21:45  clfzs  阅读(88)  评论(2)    收藏  举报