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;
}
即得易见平凡,仿照上例显然。留作习题答案略,读者自证不难。
反之亦然同理,推论自然成立。略去过程 $\rm QED$,由上可知证毕。

浙公网安备 33010602011771号