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\)通过细线连了起来。保证这些小星星通过细线可以串在一起。
输出格式:
输出共\(1\)行,包含一个整数表示可能的对应方式的数量。如果不存在可行的对应方式则输出\(0\)。
说明:
\(n<=17,m<=n*(n-1)/2\)
说起来这个题我看了很久啊。。
读题解也读了半天
始终搞不懂暴力的\(dp\)的复杂度是怎么证明的
题意:大致借用一下你谷题解的说法,求树\(T\)到图\(G\)的映射个数,要求点的映射一一对应,点引出的边在图中存在
图中每个点都被映射一次是个麻烦点,这要求我们现有映射集合作为状态存下来
于是这样暴力
\(dp_{i,j,s}\)代表\(i\)点映射为\(j\)点后,以\(i\)为子树的一个映射集合为\(s\)
转移时枚举\(S\)的子集,然后一个子树一个子树的枚举过去(复杂度似乎是乘起来的。。
判断一下有没有连边是否统计就可以了
既然每个点被映射一次很麻烦,不如去掉这个鬼限制
我们随便映射,只有恰好都映射的是合法的
考虑先减掉图中一个点没有被映射的情况,然后加上两个点没有,....
也就是说,我们把可以映射的点的每个子集的答案求出来,然后根据容斥原理加或者减就可以了
每一次的映射集合我们都做一次树形dp
复杂度:\(O(n^32^n)\)
Code:
#include <cstdio>
#define ll long long
ll dp[18][18];
int n,m,ha[18],g[18][18];
int Next[50],head[18],to[50],cnt;
void add(int u,int v)
{
to[++cnt]=v,Next[cnt]=head[u],head[u]=cnt;
to[++cnt]=u,Next[cnt]=head[v],head[v]=cnt;
}
void dfs(int now,int fa)
{
for(int i=head[now];i;i=Next[i])
{
int v=to[i];
if(v!=fa) dfs(v,now);
}
for(int i=1;i<=ha[0];i++)
{
dp[now][i]=1;
for(int j=head[now];j;j=Next[j])
{
int v=to[j];ll sum=0;
if(v==fa) continue;
for(int k=1;k<=ha[0];k++)
sum+=dp[v][k]*g[ha[i]][ha[k]];
dp[now][i]*=sum;
}
}
}
int main()
{
scanf("%d%d",&n,&m);
for(int u,v,i=1;i<=m;i++)
{
scanf("%d%d",&u,&v);
g[u][v]=g[v][u]=1;
}
for(int u,v,i=1;i<n;i++)
{
scanf("%d%d",&u,&v);
add(u,v);
}
ll ans=0;
for(int s=1;s<1<<n;++s)
{
ha[0]=0;ll sum=0;
for(int i=0;i<n;i++) if(s>>i&1) ha[++ha[0]]=i+1;
dfs(1,0);
for(int i=1;i<=ha[0];i++) sum+=dp[1][i];
if(n-ha[0]&1) ans-=sum;
else ans+=sum;
}
printf("%lld\n",ans);
return 0;
}
2018.9.7