BZOJ4671 异或图 题解

BZOJ4671 异或图

我们仍然尝试去寻找能用反演来简化运算的方法。突破口就在于题目所给的 “连通图”。所谓连通图就是连通块的个数只有 \(1\),也就是 “恰好”,那么考虑使用反演将 “恰好” 转化为 “钦定”。

具体而言,设 \(G(n)\) 表示子集异或后恰好有 \(n\) 个连通块的方案数,\(F(n)\) 表示子集异或后钦定有 \(n\) 个连通块的方案数。由于我们需要把点分成若干块,符合斯特林数的定义,所以有

\[F(n)=\sum_{i=n}^N{i\brace n}G(i) \]

所以使用斯特林反演,得到

\[G(n)=\sum_{i=n}^N(-1)^{i-n}{i\brack n}F(i) \]

由于我们需要的是 \(G(1)\),所以代入 \(n=1\)

\[G(1)=\sum_{i=1}^N(-1)^{i-1}(i-1)!F(i) \]

现在需要求 \(F(i)\)。发现我们只有 \(10\) 个点,而爆搜将 \(10\) 个点分成若干子集的方案数为 \(B_{10}\) 种,其中 \(B_n\) 是贝尔数,可以接受。那么每当我们搜出一种划分方案时,根据定义,不同连通块之间不能有边,连通块之间可任意选择。找到给定的 \(s\) 张图中连接不同连通块的这些边,将每张图的这些边状压到一个二进制数上,那么现在就要求选择任意张图使其这些边的异或和为 \(0\) 的方案数。

考虑使用线性基解决这个问题,将每张图的二进制数插入线性基。发现我们实际上要求的是一些个异或线性方程组自由元的个数,根据线性基的性质,上述的方案数就是 \(2^{s-k}\),其中 \(k\) 是线性基的大小。然后代入上文公式即可求解。

#include<bits/stdc++.h>
using namespace std;

using ll=long long;
int s,n;
bool G[61][11][11];
int fac[11],a[11];
ll ans;
void init(int n){
	fac[0]=1;
	for(int i=1;i<=n;i++) fac[i]=fac[i-1]*i;
}
struct{
	ll p[64];
	int sz;
	void ins(ll x){
		for(int i=63;~i;i--){
			if(!(x>>i)) continue;
			if(!p[i]) return p[i]=x,sz++,void();
			x^=p[i];
		}
	}
	void clear(){
		memset(p,0,sizeof p);
		sz=0;
	}
}LB;
void dfs(int now,int tp){
	if(now>n){
		LB.clear();
		for(int i=1;i<=s;i++){
			int ct=0;
			ll S=0;
			for(int j=1;j<=n;j++)
				for(int k=j+1;k<=n;k++)
					if(a[j]!=a[k])
						S|=(1ll<<ct++)*G[i][j][k];
			LB.ins(S);
		}
		ans+=fac[tp-1]*(1ll<<(s-LB.sz))*((tp-1)&1?-1:1);
		return;
	}
	for(int i=1;i<=tp+1;i++){
		a[now]=i;
		dfs(now+1,max(i,tp));
	}
}

int main(){
	cin.tie(nullptr)->sync_with_stdio(0);
	cin>>s;
	for(int i=1;i<=s;i++){
		string g;
		cin>>g;
		while(n*(n-1)>>1!=(int)g.size()) n++;
		int p=0;
		for(int j=1;j<=n;j++)
			for(int k=j+1;k<=n;k++)
				G[i][j][k]=g[p++]-'0';
	}
	init(n);
	dfs(1,0);
	cout<<ans<<'\n';
	return 0;
}
posted @ 2025-06-30 19:43  Laoshan_PLUS  阅读(244)  评论(0)    收藏  举报