题目大意:
有一张n个点,m条边的有向图,有多少个子图(选定一个边集)是没有环的。答案对1e9+7取模。(n≤17)
显然求的就是DAG个数,于是考虑一层一层的加点,但是发现显然会算重,因为并不能保证层次分明,可能有两个点为同一层的但是又分两层DP导致算重。然后我就想保存上一层的点,那么选入的新点一定要和上一层的点连边才行,这样虽然不会算重,但是不仅要打恶心的3进制DP还是一个O(4^n)的算法???? 对于这种转移顺序导致的算重,可以考虑容斥原理,具体来说,我们现在不考虑层,就一个一个的加点,那么先加A后加B和先加B后加A计算了两次,所以要剪掉AB同时被加的方案,后面同理容斥就行。。。。。。
AC Code:
#include<bits/stdc++.h>
#define maxn 17
#define mod 1000000007
using namespace std;
int n,m;
int g[maxn][maxn],bit[1<<maxn],ways[1<<maxn],way2[maxn][1<<maxn];
int dp[1<<maxn],P2[301],lg[1<<maxn];
int main()
{
scanf("%d%d",&n,&m);
for(int i=1,u,v;i<=m;i++) scanf("%d%d",&u,&v),u--,v--,g[u][v]++;
bit[0] = -1;
for(int i=1;i<(1<<n);i++) bit[i] = -bit[i - (i & -i)];
P2[0]=1;for(int i=1;i<=300;i++) P2[i] = 2ll * P2[i-1] % mod;
for(int i=0;i<maxn;i++) lg[1<<i] = i;
dp[0] = 1;
for(int i=0;i<(1<<n);i++)
{
int rsta = (1<<n) - 1 - i , las = lg[i & -i];
if(i)
for(int j=0;j<n;j++)
ways[1<<j] = way2[j][i] = way2[j][i-(1<<las)] + g[las][j];
if(rsta) for(int j=rsta & (rsta - 1);;j = (j-1) & rsta)
{
int now = j ^ rsta , res = now & -now;
ways[now] = ways[now - res] + ways[res];
dp[now | i] = (dp[now | i] + 1ll * bit[now] * dp[i] * P2[ways[now]]) % mod;
if(!j) break;
}
}
printf("%d\n",dp[(1<<n)-1]);
}