【XSY2701】异或图 线性基 容斥原理

题目描述

  定义两个图 G1 G2 的异或图为一个图 G ,其中图G的每条边在 G1 G2 中出现次数和为 1

  给你m个图,问你这 m 个图组成的集合有多少个子集的异或图为一个连通图。

  n10,m60

题解

  考虑枚举图的子集划分,让被划分到不同子集的点之间没有连边,而在同一个子集里面的点可以连通,可以不连通。

  可以用高斯消元(线性基)得到满足条件的图的个数。设枚举的子集划分有 k 个集合,那么容斥系数就是(1)k1(k1)!。并把当前的方案数乘以容斥系数计入答案。

  那么容斥系数是怎么来的呢?

  记 ci i 个集合的容斥系数。对于每一个联通块个数为j的图,对枚举到的联通块个数为 i 的方案有S(j,i)的贡献。

  我们只需要让 ni=mc(i)S(i,m)=[m=1] 就可以了。

  可以打表消元消除容斥系数。

  时间复杂度: O(Bnn2m) ,其中 Bn 是Bell数的第 n <script type="math/tex" id="MathJax-Element-21">n</script>项。

代码

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
char s[1010];
int n,m;
ull a[20][20];
int d[20];
ull ans=0;
ull pw[70];
ull fac[70];
ull c[70];
void dfs(int x,int y)
{
    if(x>n)
    {
        int i,j,k;
        for(i=0;i<m;i++)
            c[i]=0;
        for(i=1;i<=n;i++)
            for(j=i+1;j<=n;j++)
                if(d[i]!=d[j])
                {
                    ll s=a[i][j];
                    for(k=m-1;k>=0;k--)
                        if(s&(1ll<<k))
                        {
                            if(!c[k])
                            {
                                c[k]=s;
                                break;
                            }
                            s^=c[k];
                        }
                }
        int num=0;
        for(i=0;i<m;i++)
            if(!c[i])
                num++;
        ans+=pw[num]*fac[y-1]*(y&1?1:-1);
        return;
    }
    int i;
    for(i=1;i<=y;i++)
    {
        d[x]=i;
        dfs(x+1,y);
    }
    d[x]=y+1;
    dfs(x+1,y+1);
}
int main()
{
#ifndef ONLINE_JUDGE
    freopen("a.in","r",stdin);
    freopen("a.out","w",stdout);
#endif
    scanf("%d",&m);
    int i,j,k;
    int len;
    fac[0]=1;
    pw[0]=1;
    for(i=1;i<=m;i++)
        pw[i]=pw[i-1]<<1;
    for(i=1;i<=m;i++)
    {
        scanf("%s",s+1);
        if(i==1)
        {
            len=strlen(s+1);
            for(j=2;j<=10;j++)
                if(j*(j-1)/2==len)
                    break;
            n=j;
        }
        int t=0;
        for(j=1;j<=n;j++)
            for(k=j+1;k<=n;k++)
                if(s[++t]=='1')
                    a[j][k]|=1ll<<(i-1);
    }
    for(i=1;i<=n;i++)
        fac[i]=fac[i-1]*i;
    dfs(1,0);
    printf("%llu\n",ans);
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值