【BZOJ4671】异或图(斯特林反演)

【BZOJ4671】异或图(斯特林反演)

题面

BZOJ

Description

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

Input

第一行为一个整数s, 表图的个数.
接下来每一个二进制串, 第 i 行的二进制串为 gi, 其中 gi 是原图通过以下伪代码转化得
到的. 图的结点从 1 开始编号, 下面设结点数为 n.

Algorithm 1 Print a graph G = (V, E)
for i = 1 to n do
    for j = i + 1 to n do
        if G contains edge (i, j) then
            print 1
        else
            print 0
        end if
    end for
end for

$ 2 ≤ n ≤ 10,1 ≤ s ≤ 60.$

Output

输出一行一个整数, 表示方案数

Sample Input
3

1

1

0
Sample Output
4

题解

连通很难处理,正难则反,考虑总方案减去不连通。
总方案随便算,要考虑的只有不连通的方案数。
如果不连通的话,我们考虑其子集的划分,子集之间必定不存在边连通。那么我们考虑其连通块个数,设\(f_i\)表示恰好有\(i\)个连通块的方案数,\(g_i\)表示至少有\(i\)个连通块的划分的计算的结果。
考虑两者之间的关系:
\[g_k=\sum_{i=k}^n \begin{Bmatrix}i\\k\end{Bmatrix}f_i\]
原因很简单,如果直接考虑枚举连通块数量考虑划分的话,因为一个随意的连通块可能由多个连通块组成,意味着会被反复计算多次。那么对于\(k\)个连通块而言,其被计算的次数就是\(\displaystyle \begin{Bmatrix}i\\k\end{Bmatrix}\)。这个次数是考虑计算至少\(m\)的时候,多出来的连通块会被划分到其他连通块里面去,实际上等价于把当前实际存在的\(i\)个连通块划分为\(k\)个计算到至少的贡献里面去。
根据斯特林反演,可以得到:
\[f_k=\sum_{i=k}^n(-1)^{i-k}\begin{bmatrix}i\\k\end{bmatrix}g_i\]
我们要求的是恰好一个连通块的方案数,即\(f_1\)
\[\begin{aligned} f_1&=\sum_{i=1}^n(-1)^{i-1}\begin{bmatrix}i\\1\end{bmatrix}g_i\\ &=\sum_{i=1}^n(-1)^{i-1}(i-1)!g_i \end{aligned}\]
考虑如何计算\(g\)。现在要通过\(g\)来计算\(f\)。所以我们显然需要找到一个方法能够直接计算\(g\)
回到题目给定的条件,发现点数很少而图的数量很多。枚举一个子集划分,因为所求是至少,所以只需要确定不同子集之间不存在边。把这些边抠出来,要求的就是满足这些位置异或起来为\(0\)的方案数,构建线性基,答案显然是\(2^{s-c}\),其中\(c\)是线性基内元素的个数。那么全部累加到\(g\)中去最后反演计算\(f\)即可。

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
#define ll long long
#define MAX 65
#define MAXN 15
int G[MAX][MAXN][MAXN];
char ch[MAX];
int n,K,a[MAXN];
ll ans,p[MAX],jc[MAXN];
void dfs(int x,int t)
{
    if(x==n+1)
    {
        memset(p,0,sizeof(p));int ele=0;
        for(int k=1;k<=K;++k)
        {
            ll S=0;int tot=0;
            for(int i=1;i<=n;++i)
                for(int j=i+1;j<=n;++j)
                    if(a[i]!=a[j])S|=(1ll<<tot)*G[k][i][j],++tot;
            for(int i=0;i<tot;++i)
                if(S&(1ll<<i))
                {
                    if(!p[i]){p[i]=S;++ele;break;}
                    else S^=p[i];
                }
        }
        ans+=1ll*((t&1)?1:-1)*jc[t-1]*(1ll<<(K-ele));
        return;
    }
    for(int i=1;i<=t+1;++i)
        a[x]=i,dfs(x+1,max(i,t));
}
int main()
{
    scanf("%d",&K);
    for(int i=1;i<=K;++i)
    {
        scanf("%s",ch+1);int l=strlen(ch+1),cnt=0;
        for(int j=1;!n;++j)if(j*(j-1)==l+l)n=j;
        for(int j=1;j<=n;++j)
            for(int k=j+1;k<=n;++k)
                G[i][j][k]=ch[++cnt]-48;
    }
    jc[0]=1;for(int i=1;i<=n;++i)jc[i]=jc[i-1]*i;
    dfs(1,0);
    printf("%lld\n",ans);
    return 0;
}

转载于:https://www.cnblogs.com/cjyyb/p/10146389.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值