此题解只是详细一些,推荐大家先看一个更好的:题解 P3349 【[ZJOI2016]小星星】
就是该程序较机(gui)智(chu)。
题目大意:
小Y是一个心灵手巧的女孩子,她喜欢手工制作一些小饰品。她有n颗小星星,用m条彩色的细线串了起来,每条细线连着两颗小星星。
有一天她发现,她的饰品被破坏了,很多细线都被拆掉了。这个饰品只剩下了n-1条细线,但通过这些细线,这颗小星星还是被串在一起,也就是这些小星星通过这些细线形成了树。小Y找到了这个饰品的设计图纸,她想知道现在饰品中的小星星对应着原来图纸上的哪些小星星。如果现在饰品中两颗小星星有细线相连,那么要求对应的小星星原来的图纸上也有细线相连。小Y想知道有多少种可能的对应方式。
n<=17,m<=n*(n-1)/2
题目翻译:
给定一个树,用这个树覆盖一个图。求方案数。树上不同的点对应图上不同的点,就是不同的方案。
要求:
1.树上一个点对应图上一个点,一对一的关系。
2.树上相邻两个点,其对应于图上的点之间必须有连线。
换句话说,求符合要求2的树上点对于图上点壹壹映射的方案数。
分析:
又是一道树形dp题。
发现对于这个要求是很难满足的,尤其是一一对应。要是要记录一一对应的话,每个方案数还得记录子树中都选了哪些点,太麻烦了。
发现,如果这些对应点可以随便选,dp起来还是很容易的。
所以就让它先随便选,再去重。
也就是说,先找出,随便瞎选的映射的方案数(两个及以上不同的点,也可以映射同一个点)
去重,需要先减掉所有仅一个不选的方案数,也就是说,这是总数中不选i的方案数,也对应一个一定不合法的方案数。
但是发现,同时不选i,j的方案数被减了两次。因为,我们没有限制必须选择除了i的剩下所有的,只是限制了不能选i,所以减掉i的时候,就把i,j都不选的情况减了一次。j的时候又一次。
所以要加上同时不选两个的方案数。
同理,要减去不选三个的,加上不选四个的...
这,就是容斥原理。
具体做法:
用邻接矩阵存原图连通性,邻接表存树边。
我们令f[i][j]表示,在i所在的子树中,i映射到原图中的j的方案数。
对于x的一个子树y,回溯后,我们两遍循环,当f[x][i],f[y][j]时,原图中,i,j有连边的时候,就可以从f[y][j]转移到f[x][i].
当然,这里i,j都是在我们该次dfs枚举的范围之内的。
而,我们剩下的只需要枚举每次选择了哪几个数(剩下的就是不选的),这是2^n的,n<=17,可以承受。
每次枚举完,树形dp一次,n^3。
总复杂度:O(2^n*n^3)
详见代码:
#include<bits/stdc++.h> using namespace std; typedef long long ll; const int N=20; ll f[N][N]; int tot,zhan[N]; ll ans; int n,m; struct node{ int nxt,to; }bian[2*N]; int hd[N],cnt; bool con[N][N]; void add(int x,int y) { bian[++cnt].nxt=hd[x]; bian[cnt].to=y; hd[x]=cnt; } void dp(int x,int fa) { for(int i=1;i<=tot;i++) f[x][zhan[i]]=1;//首先,每个点映射zhan[i]都有至少一种方案。 for(int t=hd[x];t;t=bian[t].nxt) { int y=bian[t].to; if(y==fa) continue; dp(y,x); for(int i=1;i<=tot;i++) { ll sum=0; for(int j=1;j<=tot;j++) { if(con[zhan[i]][zhan[j]])//如果在原图中两个点相连 { sum+=f[y][zhan[j]];//这里其实是一个乘法分配律,每一种和原来方案的乘法原理做乘相加,等于所有的y的种类和,再相乘 } } f[x][zhan[i]]*=sum;//这里,乘法原理,相当于之前所有的数的方案,与这个新儿子的所有方案进行不互相影响的组合。 } } } void dfs(int x,int has)//要选第x个数,选了has个数。 { if(x>n)//一个新的选择情况 { dp(1,-1); ll sum=0; for(int i=1;i<=tot;i++) sum+=f[1][zhan[i]]; if((n-has)%2) ans-=sum;//不选奇数个,减去。 else ans+=sum; //不选偶数个,加上。 return; } tot++;zhan[tot]=x;dfs(x+1,has+1);//选此数 tot--;dfs(x+1,has);//不选 } int main() { scanf("%d%d",&n,&m); int x,y; for(int i=1;i<=n;i++) con[i][i]=1; for(int i=1;i<=m;i++) { scanf("%d%d",&x,&y); con[x][y]=1;con[y][x]=1; }//邻接矩阵 for(int i=1;i<n;i++) { scanf("%d%d",&x,&y); add(x,y);add(y,x); }//邻接表 dfs(1,0); printf("%lld",ans); return 0; }