noip 2018模擬賽2018.10.29 T2 obelist
阿新 • • 發佈:2018-11-01
又是一道玄學題...
題解:
看到資料範圍,顯然是狀壓dp
那麼我們來設計一下狀態
設dp[i]表示目前選擇的點集為i所能獲得的無環子圖個數
那麼如果要求無環,這還是個有向圖,所以我們可以將新的子圖按拓撲序分層,然後列舉每一層的狀態進行轉移
所以最淺顯的思想就是記錄整個點集的狀態,同時記錄最底層的狀態,然後用最底層的狀態進行轉移,轉移時只要求新的層與底層均有連邊即可
但是這樣做時間複雜度是O(4^n)級別的,顯然過不去所有資料
所以我們再考慮進行一些優化:
如果我們不記錄最後一層呢?
這樣會產生一些重複,所以我們用容斥解決即可:
稍微解釋一下重複的產生:由於我們並沒有記錄哪個狀態是末狀態,所以我們無法得知相同的狀態下最後一層到底是哪個點,但是我們有可能反覆地更新了同一個狀態,這樣做會產生重複
所以我們要進行一些容斥,求出容斥係數即可
然後在更新時列舉子集,邊集用2的冪次任選即可
#include <cstdio> #include <cmath> #include <cstring> #include <cstdlib> #include <iostream> #include <algorithm> #include <queue> #include <stack> #define ll long long #define mode 1000000007 using namespace std; int dp[(1<<17)+5]; int acc[20][20]; int li[(1<<17)+5]; int p[1005]; int ac[(1<<17)+5]; int kk[(1<<17)+5]; int n,m; int lowbit(int x) { return x&(-x); } int main() { freopen("obelisk.in","r",stdin); freopen("obelisk.out","w",stdout); scanf("%d%d",&n,&m); for(int i=1;i<=m;i++) { int x,y; scanf("%d%d",&x,&y); x--,y--; acc[x][y]=1; } kk[0]=-1; for(int i=1;i<(1<<n);i++) { if(i&1) { kk[i]=kk[i>>1]*(-1); }else { kk[i]=kk[i>>1]; } } p[0]=1; for(int i=1;i<=n*n;i++) { p[i]=(p[i-1]<<1)%mode; } dp[0]=1; for(int i=0;i<(1<<n)-1;i++) { for(int j=0;j<n;j++) { ac[(1<<j)]=0; } for(int j=0;j<n;j++) { if((1<<j)&i) { for(int k=0;k<n;k++) { ac[(1<<k)]+=acc[j][k]; } } } li[0]=0; ll sit=(1<<n)-1-i; ll j=sit&(sit-1); while(1) { ll t1=sit^j; ll t2=lowbit(t1); li[t1]=li[t1^t2]+ac[t2]; dp[t1|i]+=kk[t1]*p[li[t1]]*(ll)dp[i]%mode; dp[t1|i]%=mode; if(!j) { break; } (--j)&=sit; } } printf("%d\n",(dp[(1<<n)-1]%mode+mode)%mode); return 0; }