【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; }