巨神兵
阿新 • • 發佈:2021-09-13
Description
求一個 \(n\leq 17\) 個點 \(m\) 條邊的有向圖有多少個邊的子集不包含環。
Solution
邊很多,只能對點狀壓。考慮一個 DAG 可以被怎麼構造。假設 \(S\) 已經是一個拓撲圖,那麼加上一點集 \(T\),如果只從 \(S\) 向 \(T\) 連邊,那麼構造出的圖一定也還是拓撲圖。所以就可以有轉移
\[f_{S|T} \gets f_S \times 2^{cnt} \]\(cnt\) 表示 \(S\) 到 \(T\) 的有向邊個數。但是這樣會算重。我們考慮一個特定方案被計算了幾次。
例如,左邊的方案可以從右邊三種狀態分別轉移一次。擴充套件到更多的點,容易發現會被重複計算 \(2^{c}-1\)
#include<stdio.h> #include<algorithm> #include<vector> #include<queue> using namespace std; inline int read(){ int x=0,flag=1; char c=getchar(); while(c<'0'||c>'9'){if(c=='-')flag=0;c=getchar();} while(c>='0'&&c<='9'){x=(x<<1)+(x<<3)+c-48;c=getchar();} return flag? x:-x; } const int N=17; const int Mod=1e9+7; int mp[N][N],g[1<<N],in[1<<N],op[1<<N],pw[N*N],f[1<<N]; int main(){ freopen("obelisk.in","r",stdin); freopen("obelisk.out","w",stdout); int n=read(),m=read(); pw[0]=f[0]=1; for(int i=1;i<=m;i++){ int u=read(),v=read(); mp[u-1][v-1]=1,pw[i]=2ll*pw[i-1]; } int rg=(1<<n); op[0]=-1; for(int i=1;i<rg;i++) op[i]=-op[i-(-i&i)]; for(int S=0;S<rg-1;S++){ for(int i=0;i<n;i++) in[1<<i]=0; for(int i=0;i<n;i++){ if(!((S>>i)&1)) continue; for(int j=0;j<n;j++) in[1<<j]+=mp[i][j]; } const int U=rg-S-1; g[0]=0; for(int s=U&(U-1);;s=U&(s-1)){ const int now=U^s; g[now]=g[now-(-now&now)]+in[-now&now]; f[now^S]=(f[now^S]+1ll*f[S]*pw[g[now]]*op[now]%Mod+Mod)%Mod; if(!s) break; } } printf("%d",f[rg-1]); }