[ZJOI2016]小星星

题目大意:

小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;
}

猜你喜欢

转载自www.cnblogs.com/Miracevin/p/9048272.html
今日推荐