[洛谷P3349] ZJOI2016 小星星

问题描述

小Y是一个心灵手巧的女孩子,她喜欢手工制作一些小饰品。她有n颗小星星,用m条彩色的细线串了起来,每条细线连着两颗小星星。

有一天她发现,她的饰品被破坏了,很多细线都被拆掉了。这个饰品只剩下了n-1条细线,但通过这些细线,这颗小星星还是被串在一起,也就是这些小星星通过这些细线形成了树。小Y找到了这个饰品的设计图纸,她想知道现在饰品中的小星星对应着原来图纸上的哪些小星星。如果现在饰品中两颗小星星有细线相连,那么要求对应的小星星原来的图纸上也有细线相连。小Y想知道有多少种可能的对应方式。

只有你告诉了她正确的答案,她才会把小饰品做为礼物送给你呢。

输入格式

第一行包含个2正整数n,m,表示原来的饰品中小星星的个数和细线的条数。接下来m行,每行包含2个正整数u,v,表示原来的饰品中小星星u和v通过细线连了起来。这里的小星星从1开始标号。保证u≠v,且每对小星星之间最多只有一条细线相连。接下来n-1行,每行包含个2正整数u,v,表示现在的饰品中小星星u和v通过细线连了起来。保证这些小星星通过细线可以串在一起。n<=17,m<=n*(n-1)/2

输出格式

输出共1行,包含一个整数表示可能的对应方式的数量。如果不存在可行的对应方式则输出0。

样例输入

4 3
1 2
1 3
1 4
4 1
4 2
4 3

样例输出

6

解析

首先考虑暴力中的暴力:直接枚举树上的点对应的原图上点的序号,再验证是否可行。

显然这个暴力没有多大的意义,但它可以作为正解的启发。设 \(f_{i,j}\) 表示考虑 \(i\) 的子树,且 \(i\) 对应原图上的 \(j\) 的方案数。那么 \(i\) 的所有子节点必须在原图上对应与 \(j\) 相连。我们可以得到如下转移方程:

\[f_{u,i}=\prod_{v\in u}\ \ \sum_{j=1}^n f_{v,j}\times g_{i,j} \]

其中 \(g\) 是原图的邻接矩阵。但是,这样做没有考虑不能重复映射的条件。最后映射集合中的点可能少于 \(n\) 个。那接下来我们可以强制只能映射到 \(n-1\) 个点,然后用总方案数减去这里计算得到的方案数。在这个过程中,我们又会遇到一样的问题,我们要把多减的加回来。因此通过容斥原理,我们枚举映射集合,每次从图上删除不在映射集合里的点,然后 \(O(n^3)\) DP即可。

代码

#include <iostream>
#include <cstdio>
#define N 22
using namespace std;
int head[N],ver[N*2],nxt[N*2],l;
int n,m,i,j;
long long f[N][N],ans;
bool g[N][N],g1[N][N],vis[N];
int read()
{
	char c=getchar();
	int w=0;
	while(c<'0'||c>'9') c=getchar();
	while(c<='9'&&c>='0'){
		w=w*10+c-'0';
		c=getchar();
	}
	return w;
}
void insert(int x,int y)
{
	l++;
	ver[l]=y;
	nxt[l]=head[x];
	head[x]=l;
}
void dp(int x,int pre)
{
	for(int i=1;i<=n;i++) f[x][i]=1;
	for(int i=head[x];i;i=nxt[i]){
		int y=ver[i];
		if(y!=pre){
			dp(y,x);
			for(int j=1;j<=n;j++){
				long long tmp=0;
				for(int k=1;k<=n;k++){
					if(g[j][k]) tmp+=f[y][k];
				}
				f[x][j]*=tmp;
			}
		}
	}
}
void dfs(int x)
{
	if(x==n+1){
		int cnt=0;
		for(int i=1;i<=n;i++){
			if(vis[i]){
				for(int j=1;j<=n;j++) g[i][j]=g[j][i]=0;
				cnt++;
			}
		}
		dp(1,0);
		if(cnt%2==0){
			for(int i=1;i<=n;i++) ans+=f[1][i];
		}
		else{
			for(int i=1;i<=n;i++) ans-=f[1][i];
		}
		for(int i=1;i<=n;i++){
			for(int j=1;j<=n;j++) g[i][j]=g1[i][j];
		}
		return;
	}
	dfs(x+1);
	vis[x]=1;dfs(x+1);vis[x]=0;
}
int main()
{
	n=read();m=read();
	for(i=1;i<=m;i++){
		int u=read(),v=read();
		g[u][v]=g[v][u]=1;
	}
	for(i=1;i<n;i++){
		int u=read(),v=read();
		insert(u,v);insert(v,u);
	}
	for(i=1;i<=n;i++){
		for(j=1;j<=n;j++) g1[i][j]=g[i][j];
	}
	dfs(1);
	printf("%lld\n",ans);
	return 0;
}
posted @ 2020-07-27 22:16  CJlzf  阅读(150)  评论(0编辑  收藏  举报