1. 程式人生 > >noip 2018模擬賽2018.10.29 T2 obelist

noip 2018模擬賽2018.10.29 T2 obelist

又是一道玄學題...

題解:

看到資料範圍,顯然是狀壓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;
}