bzoj 4671: 异或图 容斥原理+线性基+斯特林反演

博客探讨了如何计算一组异或图的子集,使得子集的异或结果形成一个连通图。通过容斥原理和线性基的方法,分析了将节点划分为集合并避免特定边的策略。文章还提到了使用高斯消元求解主元数量以确定有效方案,并给出了问题的数学公式和代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题意

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

分析

发现要直接求连通图的话并不好求,那就考虑容斥。
先用O(贝尔数)的时间来把n个点划分成若干个集合,现在要求两两集合之间没有连边,集合内部可以任意连的方案数。
这个我们可以把每个图看做一个01变量,对于每一条连接两个集合的边,都可以对这s个集合列一个方程。然后对这些方程高斯消元一下求出主元数量tt,那么方案数就是2st
fmfm表示所有m-划分的贡献和,gmgm表示原图中恰好有m个连通块的方案数,不难得到

fm=i=mnS(i,m)gifm=∑i=mnS(i,m)gi

斯特林反演一下
gm=i=mn(1)ims(i,m)figm=∑i=mn(−1)i−ms(i,m)fi

其中S(i,m)S(i,m)是第二类斯特林数,组合意义是把ii个不同的球放入m个相同的盒子内的方案数,s(i,m)s(i,m)是第一类斯特林数,组合意义是把ii个不同的球分成m个轮换的方案数。
由于我们只要求g1g1,那么有
g1=i=1n(1)i1(i1)!fig1=∑i=1n(−1)i−1(i−1)!fi

只要通过之前的方法把所有的fifi求出来就好了。

代码

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
using namespace std;

typedef long long LL;

int n,m,bel[15];
LL bin[70],G[70],bas[70],f[15];
char str[70];

void gauss(int tot)
{
    for (int i=0;i<m;i++) bas[i]=0;
    int tim=-1,cnt=0;
    for (int x=1;x<n;x++)
        for (int y=x+1;y<=n;y++)
        {
            tim++;
            if (bel[x]==bel[y]) continue;
            LL s=0;
            for (int i=0;i<m;i++) s+=bin[i]*((G[i]&bin[tim])>0);
            for (int i=m-1;i>=0;i--)
                if (s&bin[i])
                {
                    if (bas[i]) s^=bas[i];
                    else {cnt++;bas[i]=s;break;}
                }
        }
    f[tot]+=bin[m-cnt];
}

void dfs(int x,int y)
{
    if (x>n) {gauss(y);return;}
    for (int i=1;i<=y+1;i++)
        bel[x]=i,dfs(x+1,max(y,i));
}

int main()
{
    scanf("%d",&m);
    bin[0]=1;
    for (int i=1;i<=62;i++) bin[i]=bin[i-1]*2;
    for (int i=0;i<m;i++)
    {
        scanf("%s",str);int len=strlen(str);
        for (int j=0;j<len;j++) G[i]+=str[j]=='1'?bin[j]:0;
    }
    int len=strlen(str);
    for (;n*(n-1)/2<len;n++);
    dfs(1,0);
    LL ans=0;
    for (int i=1,jc=1;i<=n;i++,jc*=i)
        if (i&1) ans+=(LL)jc/i*f[i];
        else ans-=(LL)jc/i*f[i];
    printf("%lld",ans);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值