Description
将一棵大小为n的树嵌入一个大小为n的图中,求方案数。(n<=18)
Solution
【官方题解】 :JudgeOnline/upload/201603/4455.txt
【我的理解】:
我们先考虑将树中的点用图中的点来代替,因为共有18个点,每次枚举每个图中的点能不能用,即从图中取出一个点集
S
,共有
但是,显然,我们要求的答案是每个点一一映射,然后仔细思考一下,发现可以用容斥:
计算任意标号(图中所有点随便选)的方案数 - sigma 没有标号i,剩余号任意标(也就相当于本来该标i的位置被标了一个和别的点重复的号,标号时i这个点没有出现在树中)的方案数+sigma没有标号i和j,剩余的号任意标的方案数……
这样思路正确了,可对于每一种点集,又该怎么求合法的方案数呢?
用树型DP。
设
f[i][j][S]
表示点集为S,第i个点标j的方案。枚举每个点的标号和每个点的儿子的标号转移即可。每个对应的S,sigma{
f[1][i]
}就是要求的值。这样跑了一个
O(n3)
的DP,总复杂度为
O(2nn3)
Code
#include<cstdio>
#include<cstring>
#include<cmath>
#define N 20
using namespace std;
typedef long long ll;
struct node{int to,next;}e[N*2];
int head[N],cnt,a[N],tot;
bool p[N][N];
ll f[N][N],ans;
void add_edge(int from,int to)
{
e[++cnt].next = head[from];
head[from] = cnt;
e[cnt].to = to;
}
void dp(int v,int fa)
{
for(int i = head[v];i;i=e[i].next)
if(e[i].to != fa) dp(e[i].to,v);
for(int i = 1;i <= tot;i++) {
f[v][i] = 1;
for(int j = head[v];j;j=e[j].next)
if(e[j].to != fa) {
ll t = 0;
for(int k = 1;k <= tot;k++)
if( p[a[i]][a[k]] ) t += f[e[j].to][k];
f[v][i] *= t;
}
}
}
int main()
{
int n,m,u,v;
scanf("%d%d",&n,&m);
memset(p,0,sizeof(p));
for(int i = 1;i <= m;i++) {
scanf("%d%d",&u,&v);
p[u][v] = p[v][u] = 1;
}
cnt = 0;
memset(head,0,sizeof(head));
for(int i = 1;i < n;i++) {
scanf("%d%d",&u,&v);
add_edge(u,v); add_edge(v,u);
}
int nn = 1 << n;
ans = 0;
for(int i = 1;i < nn;i++)
{
tot = 0;
for(int j = 1;j <= n;j++)
if( (i>>(j-1))&1) a[++tot] = j;
dp(1,0);
ll s = 0;
for(int j = 1;j <= tot;j++)
s += f[1][j];
ans += s * (ll)pow(-1,n-tot);
}
printf("%lld\n",ans);
return 0;
}