BZOJ4671异或图

题目描述

定义两个结点数相同的图 G1 与图 G2 的异或为一个新的图 G, 其中如果 (u, v) 在 G1 与
G2 中的出现次数之和为 1, 那么边 (u, v) 在 G 中, 否则这条边不在 G 中.
现在给定 s 个结点数相同的图 G1...s, 设 S = {G1, G2, . . . , Gs}, 请问 S 有多少个子集的异
或为一个连通图?
题解
先考虑一个dp。
对于这种连通性问题的dp我们通常是设一个f数组一个g数组,然后找到这两个数组的关系。
我们定义g[i]表示恰好有i个联通块的方案数(是指把点集分割成i个集合,不同集合之间不能连边,相同集合之间连通)。
考虑再设置f[i]表示至少有i个联通块的方案数(同上,只是相同集合之间也可以不连通)。
那么我们的转移有:f[i]=∑s(j,i)g[j]
由于我们要求g,就斯特林反演一下。
g[i]=∑(-1)j-iS(j,i)f[j]
其实我们只需要求g[1]
g[1]=∑(-1)i-1S(i,1)f[i]
g[1]=∑(-1)i-1(i-1)!f[i]
我们可以以贝尔数的复杂度枚举这n个点的集合划分,然后算出有多少种图的选择方案可以保证不同集合之间没有连边。
对于每一条跨越集合中的边,我们可以写出一个方程。
a1G1^a2G2^a3G3.....=0
ai为0/1表示这条边是否在Gi这张图中存在。
它显然是有解的(全取0就可以了
我们要做的是求解得个数,答案为2自由元
我们可以用线性基来模拟高斯消元的过程,自由元的个数就是最后线性基中0的个数。
代码
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
using namespace std;
typedef long long ll;
ll jie[11],be[11],n,G,x[63],ans;
char s[202];
int g[62][11][11];
inline int rd(){
    int x=0;char c=getchar();bool f=0;
    while(!isdigit(c)){if(c=='-')f=1;c=getchar();}
    while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=getchar();}
    return f?-x:x;
}
void solve(int now,int cnt){
    if(now>n){
        for(int i=1;i<=n;++i)
          for(int j=i+1;j<=n;++j)if(be[i]!=be[j]){
              ll tmp=0;
              for(int k=1;k<=G;++k)tmp|=(1ll*g[k][i][j]<<k-1);
            for(int k=G-1;k>=0;--k)if(tmp&(1ll<<k)){
                if(!x[k]){x[k]=tmp;break;}
                tmp^=x[k];
            } 
          }
        int num=0;
        for(int i=0;i<G;++i)if(x[i])num++;
        ans+=((cnt&1)?1:-1)*(1ll<<(G-num))*jie[cnt-1];
        for(int i=0;i<G;++i)x[i]=0;
    }
    else{
     for(int j=1;j<=cnt+1;++j){be[now]=j;solve(now+1,max(cnt,j));}
   }
}
int main(){
    G=rd();
    for(int i=1;i<=G;++i){
        scanf("%s",s+1);
        n=strlen(s+1);
        n=(1+sqrt(1+8*n))/2;int now=1;
        for(int j=1;j<=n;++j)for(int k=j+1;k<=n;++k){g[i][j][k]=s[now]-'0';now++;} 
    } 
    jie[0]=1;
    for(int i=1;i<=n;++i)jie[i]=jie[i-1]*i;
    solve(1,0);
    cout<<ans;
    return 0;
}
原文地址:https://www.cnblogs.com/ZH-comld/p/10396736.html