BZOJ4671 异或图

Description

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

Solution

考虑类似容斥的做法,计算图不连通的情况数

设$f_i$表示将图分为$i$个部分,这$i$个部分之间没有连边,每个部分内部不做要求的方案数,$g_i$表示图分为恰好$i$个连通块的方案数

那么有

$$f_i=\sum _{j=i}^n \begin{Bmatrix}j \\i \end{Bmatrix}g_i$$

斯特林反演:

$$\displaystyle f(n)=\sum_{k=0}^n \begin{Bmatrix}n\\k \end{Bmatrix}g(k)\Longleftrightarrow g(n)=\sum_{k=0}^n(-1)^{n-k}\begin {bmatrix} n\\k \end{bmatrix}f(k)$$

可得(虽然是向上枚举但是应该也对)

$$g_i=\sum _{j=i}^n (-1)^{j-i}\begin{bmatrix}j \\i \end{bmatrix}f_j$$

题中要求的是$g_1=\sum _{i=1}^n (-1)^{i-1}(i-1)!f_i$

问题转化为求$f_i$

可以想到对于确定的分组,每个部分之间的边在一个图中存在状态是确定的

所以可以用线性基,向其中加入01串,每个串表示对应的图中部分之间连边是否存在

那么就是求这些01串可以异或出0的方案数,设线性基中数的个数为$c$

答案为$2^{s-c}$

在$Bell(n)$复杂度内搜索即可

在将数加入线性基时发现的:将一个01矩阵每一行看作一个数和每一列看作一个数加入线性基,线性基中数的个数相等

最后的解释是转置矩阵的秩等于原矩阵的秩

#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
using namespace std;
int s,len,n,tot,g[15];
long long fac[15]={1},base[70],temp,ans,map[65][15][15],b[65];
char str[105];
inline int read()
{
    int f=1,w=0;
    char ch=0;
    while(ch<'0'||ch>'9'){if(ch=='-') f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9') w=(w<<1)+(w<<3)+ch-'0',ch=getchar();
    return f*w;
}
void dfs(int k,int top)
{
    if(k==n)
    {
        memset(base,0,sizeof(base));
        for(int i=1;i<=s;i++)
        {
            temp=0;
            int cnt=0;
            for(int j=1;j<=n;j++) for(int l=j+1;l<=n;l++) if(g[j]!=g[l]) temp|=map[i][j][l]<<cnt,++cnt;
            for(int j=50;~j;j--) if(temp>>j&1)
                if(base[j]) temp^=base[j];
                else
                {
                    base[j]=temp;break;
                }
        }
        int cnt=0;
        for(int l=0;l<50;l++) cnt+=(base[l]>0);
        if(top&1) ans+=(1ll<<s>>cnt)*fac[top-1];
        else ans-=(1ll<<s>>cnt)*fac[top-1];
        return;
    }
    for(int i=1;i<=top+1;i++) g[k+1]=i,dfs(k+1,max(top,g[k+1]));
}
int main()
{
    s=read();
    for(int i=1;i<=s;i++)
    {
        scanf("%s",str),len=strlen(str),n=(1+sqrt(1+8*len))/2,tot=0;
        for(int j=1;j<=n;j++) for(int k=j+1;k<=n;k++) map[i][j][k]=str[tot++]-'0';
    }
    for(int i=1;i<=n;i++) fac[i]=fac[i-1]*i;
    dfs(0,0);
    printf("%lld\n",ans);
    return 0;
}
异或图

 

posted @ 2020-12-05 16:21  QDK_Storm  阅读(139)  评论(0编辑  收藏  举报