bzoj4671: 异或图

感觉跟这题很像bzoj2839: 集合计数

首先这个和子集没有关系,但和点的划分有关

考虑枚举点的划分,设gi表示把点分成至少分成i个块的方案数,fi去掉至少

和那题类似的想法,对于大小为x的划分,它对gi的贡献为S2(i,x)

所以有gi=sigema(1~i)x S2(i,x)*fi

上斯特林反演

fi=sigema(1~i)x (-1)^(i-x) * S1(i,x) * gi

只需要算f1,即算 sigema(1~i)x (-1)^(i-1) * S1(i,1) * gi = sigema(1~i)x (-1)^(i-1) * (i-1)! * gi

所以就是算gi就可以了,爆搜划分,把必须要不联通的搞出来,对于每个图是一个异或方程,解异或方程组,计算解的方案数即自由元个数,这里没必要高斯消元,用线性基判断就可以了

注意假如是nm^2枚举边和点check会T,要加上时间戳,一个是在线性基里这个基是否存在,一个是当前这条边是否必须要不联通,这样下来复杂度就被卡到m^2+n*m,勉强过了,bzoj中游水准

 

注意斯特林反演x的下标从0从1开始都没有区别,因为斯特林数除了0,0是1其他i,0都是0   ORZ%%%%zory神犇

#include<cstdio>
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<cmath>
#include<bitset>
using namespace std;
typedef long long LL;
const int maxn=110;
const int maxm=20;
LL fac[maxm];
void pre(){fac[0]=1;for(int i=1;i<=maxm;i++)fac[i]=fac[i-1]*i;}

//-------------------------------------------def----------------------------------------------------------

int n,m,len;bitset<maxm*maxm>mp[maxn],lt[maxm*maxm],p;
int tim,v[maxm*maxm],ti[maxm*maxm];
bool insert(int t)
{
    p=mp[t];
    for(int i=len;i>=1;i--)
        if(p[i]==1&&v[i]==tim)
        {
            if(ti[i]!=tim)
            {
                ti[i]=tim;
                lt[i]=p;
                return true;
            }
            else p^=lt[i];
        }
    return false;
}

//------------------------------------------线性基--------------------------------------------------------

int num,b[maxm];LL ans;
void dfs(int k)
{
    if(k==m+1)
    {
        len=0; tim++;
        for(int i=1;i<=m;i++)
            for(int j=i+1;j<=m;j++)
            {
                len++; 
                if(b[i]!=b[j])v[len]=tim;
            }
        int sum=0;
        for(int t=1;t<=n;t++)
            sum+=(insert(t)==false);
        
        ans+=(num%2==0?-1:1)*fac[num-1]*(1LL<<sum);
        return ;
    }
    
    for(int i=1;i<=num;i++)
    {
        b[k]=i;
        dfs(k+1);
    }
    b[k]=++num;
    dfs(k+1);
    num--;
}

char ss[maxm];
int main()
{
    freopen("a.in","r",stdin);
    freopen("a.out","w",stdout);
    pre();
    scanf("%d",&n);
    for(int t=1;t<=n;t++)
    {
        scanf("%s",ss+1),m=strlen(ss+1);
        for(int i=1;i<=m;i++)mp[t][i]=(ss[i]=='1');
    }
    for(int i=2;i<=10;i++)if(m==i*(i-1)/2){m=i;break;}
    dfs(1);
    printf("%lld\n",ans);
    
    return 0;
}

 

posted @ 2019-01-28 11:25  AKCqhzdy  阅读(146)  评论(0编辑  收藏  举报