1. 程式人生 > >[ZJOI2016]小星星

[ZJOI2016]小星星

長度 string 多重 def 但是 brush 去那 很多 sizeof

動態規劃$+$容斥原理:

1、暴力狀壓

我們嘗試設計狀態:$$f[i][j][S]$$

表示新的圖上點$i$對應舊的圖上點$j$並且所有點的狀態為一個二進制數$S$時的方案數

但是這樣做為什麽說是暴力呢。。。一看就知道,復雜度爆炸,然而我並不會證$qwq$

2、正解(可能吧):容斥原理

我們思考上面這種方法為什麽會爆炸,就是因為有了最後那維狀態,如果我們可以省去那個狀態的話,就可以降低時間復雜度了,於是我們設:

$$f[i][j]$$表示新的圖上點$i$對應舊的圖上點$j$時的方案數

那麽我們就面臨一個問題,就是有很多重復的狀態我們都算進來了,那麽我們就考慮用容斥原理就好啦

我們發現只要集合長度:$|S|=n$時的方案數減去$|S|=n-1$時的方案數加上$|S|=n-2$時的方案數減去$|S|=n-3$時的方案數$......$

一加一減就闊以了

代碼:

#include<iostream>
#include<cstdio>
#include<cstring> 
#define ll long long
#define N 19
#define M 140
using namespace std;
struct Edge
{
	int to,nxt;
}edge[N<<1];
int n,m,cnt;
ll ans;
int ban[N],head[N],g[N][N];
ll f[N][N];
void Add(int u,int v)
{
	edge[++cnt]=(Edge){v,head[u]};
	head[u]=cnt;
}
void Dfs(int u,int fa)
{
	for(int i=1;i<=n;++i)
		f[u][i]=1;
	for(int i=head[u];i;i=edge[i].nxt)
	{
		int v=edge[i].to;
		if(v==fa)
			continue;
		Dfs(v,u);
		for(int j=1;j<=n;++j)
		{
			ll sum=0;
			for(int k=1;k<=n;++k)
				sum+=f[v][k]*(g[k][j]&&ban[j]&&ban[k]);
			f[u][j]*=sum;
		}
	}
}
signed main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;++i)
	{
		int u,v;
		scanf("%d%d",&u,&v);
		g[u][v]=g[v][u]=1;
	}
	for(int i=1;i<n;++i)
	{
		int u,v;
		scanf("%d%d",&u,&v);
		Add(u,v);
		Add(v,u);
	}
	for(int i=0;i<(1<<n);++i)
	{
		memset(ban,0,sizeof(ban));
		ll ret=0;
		int size=n,now=i;
		for(int j=1;now;now>>=1,++j)
			ban[j]=(now&1),size-=(now&1);
	//	for(int j=1;j<=n;++j)
	//		printf("%d ",ban[j]);
	//	printf("\n");
		Dfs(1,0);
		for(int j=1;j<=n;++j)
			ret+=f[1][j];
		if(size%2)
			ans-=ret;//printf("%d\n",ans);
		else
			ans+=ret;
	}
	printf("%lld",ans);
	return 0;
}

  

[ZJOI2016]小星星