【BZOJ4455】小星星(ZJOI2016)-树形DP+容斥原理
测试地址:小星星
做法:本题需要用到树形DP+容斥原理。
我省省队队长Mychael曰:假紫题,水题。orz。
这题要求的是,对一棵树上每个点求一个映射,使得每一条树边在映射到一个图上后仍存在,求方案数。容易想到以下状态定义:
令为以为根的子树,映射到,并且子树中的点映射到的点集为的方案数。
然后就可以转移了。然而这个DP是的,无法通过此题,所以我们要想一个新的办法。
注意到我们把一维略掉后,求出的方案数是没有“两个不同的点映射到的点不同”这一限制的方案数。这个条件实际上等价于原图中的每个点都要被映射到,于是想到容斥,枚举一个点集并强制这个点集内的点不能被映射到,然后做一遍上面的DP,按照容斥的式子计算即可。这样时间复杂度就是的,开O2可以过掉此题。
以下是本人代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int n,m,first[20]={0},firstt[20]={0},tot=0;
ll dp[20][20];
struct edge
{
int v,next;
}e[1000],t[1000];
void insert(int a,int b) {e[++tot].v=b,e[tot].next=first[a],first[a]=tot;}
void insertt(int a,int b) {t[++tot].v=b,t[tot].next=firstt[a],firstt[a]=tot;}
void DP(int now,int v,int fa)
{
for(int i=firstt[v];i;i=t[i].next)
if (t[i].v!=fa) DP(now,t[i].v,v);
for(int i=1;i<=n;i++)
if (now&(1<<(i-1)))
{
dp[v][i]=1;
for(int p=firstt[v];p;p=t[p].next)
if (t[p].v!=fa)
{
ll sum=0;
for(int q=first[i];q;q=e[q].next)
if (now&(1<<(e[q].v-1))) sum+=dp[t[p].v][e[q].v];
dp[v][i]*=sum;
}
}
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++)
{
int a,b;
scanf("%d%d",&a,&b);
insert(a,b),insert(b,a);
}
tot=0;
for(int i=1;i<n;i++)
{
int a,b;
scanf("%d%d",&a,&b);
insertt(a,b),insertt(b,a);
}
ll ans=0;
for(int i=1;i<(1<<n);i++)
{
DP(i,1,0);
int x=i,tot=0;
while(x)
{
if (x&1) tot++;
x>>=1;
}
ll f=1;
if ((n-tot)%2) f=-1;
for(int j=1;j<=n;j++)
if (i&(1<<(j-1))) ans+=f*dp[1][j];
}
printf("%lld",ans);
return 0;
}