bzoj4671 异或图(容斥原理 + 第二类斯特林数 + 高斯消元)

bzoj4671 异或图

原题地址http://www.lydsy.com/JudgeOnline/problem.php?id=4671

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

数据范围
2 ≤ n ≤ 10,1 ≤ s ≤ 60

题解:
好题。
n的范围是10,那么边的范围为50,显然不能直接枚举边的子集再看是否联通。
而考虑到10的贝尔数是一个可枚举的范围,可以枚举点的划分计算方案然后容斥。
即,不同集合内的点没有边,相同集合内的点任意连的方案数,
对于这个方案数,我们只需要跑个高斯消元,解的数量就是2(s|B|)

(关于2(s|B|) 我的理解是和线性基相同,最后消出来得到的s|B| 个自由元,对于每个数(在此处二进制位上表示的是每个图的存在情况),表示的方法是唯一的。后面被消为0的数一定可以保留的主元表示出来,2(s|B|) 即异或为0的集合数量,与原来那一个解异或得到的值相同,2(s|B|) 就是解的数量)
UPD:我是在误人子弟了…实际上是完全不一样的。对于此题,可以说是最后剩多少个主元,就有多少个图的存在性是确定的,那么剩下的随意有无就是2(s|B|)

对于容斥系数我们得到:
i=1m{ im}f(i)=[m==1]
f(i) 可以m2 递推。(或者打个表发现f(i)=(1)i1(i1)! ,代回去可以证出来)

于是用贝尔数的时间枚举子集划分,然后跑高消,乘上对应的容斥系数计入答案即可,O(B(n)n2s)
( 注意:此代码的高消部分存在问题,暂未改正,请勿参考)
代码:

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#define LL long long
using namespace std;
const int N=15;
const int SS=61;
char str[105];
int g[SS][N][N],s,n,S[N][N],f[N],col[N];
LL pw[SS],ans=0,C[SS];
void dfs(int x,int sz)
{
    if(x==n+1)
    {
        int tot=0;
        for(int i=1;i<=n;i++)
        for(int j=i+1;j<=n;j++)
        {
            LL cur=0; 
            if(col[i]!=col[j])
            {
                for(int ss=1;ss<=s;ss++)
                if(g[ss][i][j]) cur+=pw[ss-1];
                for(int k=1;k<=tot;k++)
                if((cur^C[k])<cur) cur=cur^C[k];
                if(cur) C[++tot]=cur;
            }
        }
        ans+=1LL*f[sz]*pw[s-tot];
        return;
    }
    for(int i=1;i<=sz;i++) {col[x]=i; dfs(x+1,sz);}
    col[x]=sz+1; dfs(x+1,sz+1);
}
int main()
{
    scanf("%d",&s);
    for(int ss=1;ss<=s;ss++)
    {
        scanf("%s",str); int len=strlen(str);
        for(n=1;;n++) if(n*(n-1)==2*len) break;
        for(int l=0,i=1;i<=n;i++)
        for(int j=i+1;j<=n;j++)
        g[ss][i][j]=g[ss][j][i]=str[l++]-'0';
    }
    for(int i=0;i<=s;i++) pw[i]=1LL<<i;
    for(int i=0;i<=n;i++) for(int j=0;j<=i;j++)
    if(i==j) S[i][j]=1; else S[i][j]=S[i-1][j-1]+j*S[i-1][j];
    f[1]=1;
    for(int i=2;i<=n;i++)
    for(int j=1;j<i;j++) f[i]-=f[j]*S[i][j];    
    dfs(1,0);
    printf("%lld\n",ans);
    return 0;
}
发布了203 篇原创文章 · 获赞 43 · 访问量 5万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章