Problem

给出一个有n个点m条边的有向图,问其有多少子图是有向无环图。
1n17

Step1

比赛时我想到了一个很直观的想法,首先,有向无环图是可以分层的,用二进制状态记录当前已经用过的点和最后一层的点,然后枚举下一层的点的状态,于是可以进行DP,时间复杂度O(4n)

Step2

上面一种方法的局限性在于,它要保留最后一层的点的状态,那么是否可以不用管最后一层的点的状态呢?由于这样直接转移,有的点可能会被计算到下一层中去,这是容斥的模型,对于当前状态S,我们枚举最后一层的点的状态T,如果T中有奇数个点,那么这样的贡献是1,如果是偶数,这样的贡献就是-1

Code

#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cmath>
#include<cstring>#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)using namespace std;int get(){char ch;int s=0;bool bz=0;while(ch=getchar(),(ch<'0'||ch>'9')&&ch!='-');if (ch=='-')bz=1;else s=ch-'0';while(ch=getchar(),ch>='0'&&ch<='9')s=s*10+ch-'0';if (bz)return -s;return s;
}const int N = 20;
const int L = 131200;
const int mo = 1e+9+7;typedef long long LL;int u[N][L],mi[N*N];
LL f[L];
int n,m;
bool mp[N][N];
bool bz[N];
int w[N],k,now;void dfs(int x,bool sig,int v,int t){if (x>k){if (!t)return;if (sig)f[now|t]=(f[now|t]+LL(f[now])*mi[v]%mo)%mo;else f[now|t]=(f[now|t]+mo-LL(f[now])*mi[v]%mo)%mo;return;}dfs(x+1,sig,v,t);dfs(x+1,sig^1,v+u[w[x]][now],t|mi[w[x]-1]);
}int main(){freopen("obelisk.in","r",stdin);freopen("obelisk.out","w",stdout);n=get();m=get();fo(i,1,m){int x=get(),y=get();mp[x][y]=1;}mi[0]=1;fo(i,1,m)mi[i]=mi[i-1]*2%mo;fo(i,1,n)fo(j,0,mi[n]-1)fo(x,1,n)if ((j&mi[x-1])>0&&mp[x][i])u[i][j]++;memset(f,0,sizeof(f));f[0]=1;fo(i,0,mi[n]-1){k=0;int x=mi[n]-1-i;while(x){w[++k]=floor(log(x&-x)/log(2))+1;x-=x&-x;}now=i;dfs(1,0,0,0);}printf("%d\n",f[mi[n]-1]);fclose(stdin);fclose(stdout);return 0;
}