洛谷 P3349 [ZJOI2016]小星星 解题报告

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

posted @ 2018-09-07 17:06  露迭月  阅读(357)  评论(0编辑  收藏  举报