zoukankan      html  css  js  c++  java
  • [ZJOI2016]小星星

    此题解只是详细一些,推荐大家先看一个更好的:题解 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;
    }
  • 相关阅读:
    PAT B1045 快速排序 (25 分)
    PAT B1042 字符统计 (20 分)
    PAT B1040 有几个PAT (25 分)
    PAT B1035 插入与归并 (25 分)
    PAT B1034 有理数四则运算 (20 分)
    PAT B1033 旧键盘打字 (20 分)
    HDU 1231 最大连续子序列
    HDU 1166 敌兵布阵
    HDU 1715 大菲波数
    HDU 1016 Prime Ring Problem
  • 原文地址:https://www.cnblogs.com/Miracevin/p/9048272.html
Copyright © 2011-2022 走看看