【2016NOIP十连测】【test4】【状压DP】【容斥原理】巨神兵

题目大意:

  给一个n个点(n<=17),m条边的有向图(无自环、无重边),求其无环子图的方案数。

题解:

  看到n<=17,显然是用状压dp。

  用f[i]表示点集i的满足条件的方案数。

  状态转移时可以一层一层地把点加进点集。

  具体的,枚举已知点集i,在i的补集中枚举下一层的点集j(可以无边相连)(以下i,j定义相同),

  统计由i连向j的边e。对于这些边,每一条都可以选或不选,

  就有2e种情况,即:

    f[i^j]+=f[i]*2e;

  这显然是错误的,

  因为对于点集k的一种连边方案,可以有多种方式划分成i,j,

  这就意味着不同的i,j可能包含了同样的方案。

  于是以j中包含的元素把并集为k的i和j分类

  转移的时候多乘个容斥系数就可以了,即:

    f[i^j]+=f[i]*2e*(-1)size(j)+1;

  复杂度O(3nm)可优化成O(3n+2nm);

#include<cstdio>
#include<iostream>
using namespace std;
typedef long long ll;
const int N = 17;
const int M = 300;
const int P = 1e9+7;

ll read(){
    ll re=0;bool neg=0;char ch=getchar();
    while (!isdigit(ch)) {if (ch=='-') neg=1;ch=getchar();}
    while (isdigit(ch)) re=re*10+ch-'0',ch=getchar();
    if (neg) re=-re; return re;
}

int n,m,U;
int p2[M],coe[1<<N],g[N][N];
int f[1<<N],deg[1<<N],sum[1<<N];

void readin(){
    n=read(),m=read();
    for (int i=0;i<m;i++)
        g[read()-1][read()-1]=1;
    U=(1<<n)-1;
}

void prework(){
    p2[0]=1;coe[0]=-1;
    for (int i=1;i<=m;i++){
        p2[i]=p2[i-1]<<1;
        if (p2[i]>=P) p2[i]%=P;
    }
    for (int i=1;i<(1<<n);i++){
        coe[i]=coe[i>>1];
        if (i&1) coe[i]*=-1;
    }
}

void dp(){
    f[0]=1;
    for (int i=0;i<U;i++){
        for (int j=0;j<n;j++)deg[1<<j]=0;
        for (int j=0;j<n;j++)if ((1<<j)&i)
            for (int k=0;k<n;k++) deg[1<<k]+=g[j][k];
        int C=U^i;sum[0]=0;
        for (int tmp=(C-1)&C;;tmp=(tmp-1)&C){
            int Now=C^tmp;
            sum[Now]=sum[Now-(Now&-Now)]+deg[Now&-Now];
            f[i^Now]=(f[i^Now]+(ll)f[i]*p2[sum[Now]]*coe[Now])%P;
            if (!tmp) break;
        }
    }
}

int main(){
    readin();
    prework();
    dp();
    printf("%d\n",f[U]);
    return 0;
}
View Code

 

posted @ 2016-10-09 17:05  KaNNe乄羽  阅读(709)  评论(0编辑  收藏  举报