[ZJOI2016]小星星

洛谷题目链接

动态规划$+$容斥原理:

1、暴力状压

我们尝试设计状态:$$f[i][j][S]$$

表示新的图上点$i$对应旧的图上点$j$并且所有点的状态为一个二进制数$S$时的方案数

那么只需要暴力树形$dp$就行了

但是这样做为什么说是暴力呢。。。一看就知道,复杂度爆炸,然而我并不会证$qwq$

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

我们思考上面这种方法为什么会爆炸,就是因为有了最后那维状态,如果我们可以省去那个状态的话,就可以降低时间复杂度了,于是我们设:

$$f[i][j]$$表示新的图上点$i$对应旧的图上点$j$时的方案数

那么我们就面临一个问题,我们只需要$n$个点在集合内的方案数,但是$n-1,n-2,cdots$的点的方案数我们都算进来了

那么我们算一下$n-1$的答案,这些方案数包括$n-2,n-3,cdots$等的方案数

但是我们发现我们总共减了两次$n-2$的方案数,所以再加上$n-2$的答案,但是又多了$n-3$等,我们发现:一加一减到最后刚好就是我们要的$n$个点的方案数

所以:$|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("
");
		Dfs(1,0);
		for(int j=1;j<=n;++j)
			ret+=f[1][j];
		if(size%2)
			ans-=ret;//printf("%d
",ans);
		else
			ans+=ret;
	}
	printf("%lld",ans);
	return 0;
}

  

原文地址:https://www.cnblogs.com/yexinqwq/p/10254401.html