很是神仙的狀壓DP+容斥原理。ios
首先,給出一個狀壓方程:$f_S$表示點集爲$S$的狀況下,整個點集構成強連通圖的方案數。spa
這個DP方程仍是比較容易想到的,可是沒有辦法正常轉移,考慮經過容斥原理進行轉移。cdn
對於一個點集,它沒法構成強連通份量的方案,就是咱們選擇一個出度爲$0$的強連通份量,這個強連通份量並不包含總體的方案,就是沒法構成的方案數,也就是縮點後的圖是一個至少兩個節點的DAG。blog
那麼,咱們能夠欽定一個點集$j,j\subset S$做爲出度爲$0$的強連通份量,那麼能夠獲得,這樣其餘的不是從這個點集連向其餘點集的邊是隨意選取的,也就是$2^p$種方案。文檔
可是因爲咱們不知道$S-j$的部分中有沒有出現出度爲$0$的強連通份量,若是出現,那麼就重複計算了。get
因此考慮容斥,$t_i$表示$i$點集構成點數大於1的DAG的方案數,$t_S=\sum\limits_{j\subseteq S,j\ne 0}{(-1)^{|j|-1}\times 2^{way_{i-j,j}}}\times t_{i-j}$,其中$way_{j,i-j}$表示$i-j$點集向$j$點集連接的邊,$|j|$表示選擇的強連通份量集的強連通份量數量。string
上面的式子應該會很好理解,就很少解釋了,那麼根據上面的式子,咱們提出一個建設性的想法,$g_i$表示$i$的點集中,構成奇數個互不連通強連通份量-構成偶數個互不連通強連通份量的方案數,也就是說在狀態中直接包含容斥係數,那麼能夠給出方程:$g_S=f_S-\sum\limits_{j\subseteq S,u\in j}g_{S-j}\times f_j$it
這個式子的正確性關鍵在於$g_S$構成的強連通份量是互不連通(由於出度所有爲$0$)的,因此這樣轉移顯然是正確的。io
那麼接下來考慮$f_S$如何更新。class
一樣,咱們回到上面的式子,$t_S$能夠表達爲:$t_S=\sum\limits_{j\subset i,j\ne 0}g_j\times 2^{sum_S-w_j}$,$w_j$表示$j$連向$S$的邊數。
而後,$f_S=2^{sum_S}-t_S$
而後咱們就解決這道題啦!撒花撒花✿✿ヽ(°▽°)ノ✿
https://files-cdn.cnblogs.com/files/Winniechen/BZOJ3812.pdf
#include <cstdio> #include <algorithm> #include <cmath> #include <cstring> #include <cstdlib> #include <queue> #include <iostream> #include <bitset> using namespace std; #define N (1<<15)+20 #define ll long long #define mod 1000000007 int f[N],g[N],cnt[N],in[N],out[N],n,m,b[N],w[N],sum[N]; void dfs(int i,int j) { if(i&(j-1))dfs(i,i&(j-1)); w[j]=w[j-(j&-j)]+cnt[in[j&-j]&i]; } int main() { scanf("%d%d",&n,&m);b[0]=1; for(int i=1,x,y;i<=m;i++) { scanf("%d%d",&x,&y);x--,y--; in[1<<x]|=1<<y,out[1<<y]|=1<<x; b[i]=(b[i-1]<<1)%mod; } for(int S=1;S<1<<n;S++) { int x=S&-S,s=S^x;cnt[S]=cnt[s]+1;sum[S]=sum[s]+cnt[in[x]&S]+cnt[out[x]&S]; dfs(S,S);f[S]=b[sum[S]]; for(int j=s;j;j=s&(j-1))g[S]=(g[S]-(ll)f[S^j]*g[j])%mod; for(int j=S;j;j=S&(j-1))f[S]=(f[S]-(ll)g[j]*b[sum[S]-w[j]])%mod; g[S]=(g[S]+f[S])%mod; } printf("%d\n",(f[(1<<n)-1]+mod)%mod); }