【BZOJ 4671】异或图

题目链接

题目描述

定义两个结点数相同的图 G1 与图 G2 的异或为一个新的图 G, 其中如果 (u, v) 在 G1 与
G2 中的出现次数之和为 1, 那么边 (u, v) 在 G 中, 否则这条边不在 G 中.
现在给定 s 个结点数相同的图 G1…s, 设 S = {G1, G2, . . . , Gs}, 请问 S 有多少个子集的异
或为一个连通图?

Sol

首先正难则反,考虑不连通

观察到点数很小,我们可以暴力枚举每一个点的连通情况 , 设 f i f_i fi 表示有 i i i 个连通块的方案数 , 往容斥方面考虑 , 设 g i g_i gi 表示至少有 i i i 个连通块的方案:
由于我们没有考虑内部的连通性,那么一个(假的)连通块内可能包含的是多个连通块,容易看出,假设我们真正有 n n n 个连通块,但是我们却只认为它有 m m m 个连通块,那么就会把这种情况算 S ( n , m ) S(n,m) S(n,m) 次,相当于把连通块分组组合,那么斯特林反演:
g m = ∑ i = m n { i m } f i f m = ∑ i = m n ( − 1 ) i − m [ i m ] g i g_m=\sum_{i=m}^n \left \{ \begin{matrix}i\\m\end{matrix} \right \}f_i\\ f_m=\sum_{i=m}^n (-1)^{i-m}\left [ \begin{matrix}i\\m\end{matrix} \right ]g_i\\ gm=i=mn{im}fifm=i=mn(1)im[im]gi

要求的是 f 1 f_1 f1 ,系数就变成了阶乘
这样我们就能够通过枚举连通块,用线性基来求出 g g g, 具体怎么求:
我们先把当前连通块情况下有用的边拿出来(就是端点不在同一个连通块内) , 没用的置为0,然后把这个状态丢到线性基里面
假设 我们总共插入 s s s 个,最后只插进去了 m m m 个,也就是基的大小是 m m m , 那么最后没有插进去的 s − m s-m sm 个,一定是因为中间变成了 0 0 0 , 也就是在原来的线性基里面有能够异或出它的,并且线性基里面一定都是有用的元素,那么能够异或出 0 0 0 就当且仅当选择了没有被插进去的元素,那么方案数为 2 s − m 2^{s-m} 2sm (算上了空集,因为全部不选就行了)

代码:

#include
using namespace std;
int n,s;
typedef long long ll;
const int N=61;
const int MAXN=1e2+10;
const int MAXM=1<<10;
ll G[N];
char S[MAXN];
int fac[20],m;
int cnt=0,bel[20];
int tot=0;
ll bac[N];
ll g[20];
inline void Clear(){for(int i=0;i<m;++i) bac[i]=0;tot=0;}
inline void insert(ll x){
	for(int i=m-1;~i;--i){
		if(!((x>>i)&1)) continue;
		if(!bac[i]) {bac[i]=x;++tot;break;}
		else x^=bac[i];
	}
	return;
}
void dfs(int u){//搜索集合划分
	bel[u]=0;
	if(u>n) {
		int head=0;ll v=0;
		for(int i=1;i<=n;++i)
			for(int j=i+1;j<=n;++j){
				++head;if(bel[i]!=bel[j]) v|=1ll<<(head-1);
			}
		Clear();
		for(int i=1;i<=s;++i) {ll p=v&G[i];insert(p);}
		g[cnt]+=1ll<<(s-tot);
		return;
	}
	if(!cnt) cnt=1,bel[u]=1,dfs(u+1),cnt=0;
	else {
		for(int i=1;i<=cnt;++i) {bel[u]=i;dfs(u+1);}
		++cnt;bel[u]=cnt;
		dfs(u+1);--cnt;
	}
	return;
}
int main()
{
	cin>>s;int head=0;
	for(int i=1;i<=s;++i){
		cin>>(S+1);m=strlen(S+1);head=0;
		n=(1+sqrt(1+8*m))/2;
		for(int j=1;j<=n;++j) {
			for(int k=j+1;k<=n;++k) {++head;
				G[i]|=(S[head]-'0')*1ll<<(head-1);
			}
		}
	}fac[0]=1;
	for(int i=1;i<=n;++i) fac[i]=fac[i-1]*i;
	dfs(1);ll ans=0;
	for(int i=1;i<=n;++i){(i&1)? (ans+=g[i]*fac[i-1]):(ans-=g[i]*fac[i-1]);}
	cout<<ans<<endl;
}

你可能感兴趣的:(======题解======,线性基,容斥原理,斯特林反演)