AtCoder Regular Contest 101 E - Ribbons on Tree 树形dp+容斥原理

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_33229466/article/details/82261499

题意

给一棵树,现在可以把节点之间两两配对,问有多少种配对方案满足每一条边都至少在某一对节点的最短路径上。
n 5000

分析

很容易想到树形dp:设 f i , j 表示以i为根的树中,还有j个点没有配对的方案。这样做显然是 O ( n 3 ) 的,然后又发现没办法优化,于是就只能另寻出路。
考虑容斥,我们可以枚举有哪些边一定没有被覆盖,那么容斥系数就是 ( 1 ) ,方案就是将这些边去掉后,每个连通块内的点随意配对的方案。
那么我们就可以树形dp了。
f i , j 表示以i为根的子树中,i所在的连通块内还有j个点的方案数。
转移显然。

代码

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>

typedef long long LL;

const int N=5005;
const int MOD=1000000007;

int n,cnt,last[N],f[N][N],g[N],tmp[N],size[N];
struct edge{int to,next;}e[N*2];

void addedge(int u,int v)
{
    e[++cnt].to=v;e[cnt].next=last[u];last[u]=cnt;
    e[++cnt].to=u;e[cnt].next=last[v];last[v]=cnt;
}

void dfs(int x,int fa)
{
    size[x]=f[x][1]=1;
    for (int i=last[x];i;i=e[i].next)
    {
        if (e[i].to==fa) continue;
        dfs(e[i].to,x);
        for (int j=1;j<=size[x]+size[e[i].to];j++) tmp[j]=0;
        for (int j=1;j<=size[x];j++)
            for (int k=1;k<=size[e[i].to];k++)
            {
                (tmp[j+k]+=(LL)f[x][j]*f[e[i].to][k]%MOD)%=MOD;
                if (!(k&1)) (tmp[j]+=MOD-(LL)f[x][j]*f[e[i].to][k]%MOD*g[k]%MOD)%=MOD;
            }
        size[x]+=size[e[i].to];
        for (int j=1;j<=size[x];j++) f[x][j]=tmp[j];
    }
}

int main()
{
    scanf("%d",&n);
    for (int i=1;i<n;i++)
    {
        int x,y;scanf("%d%d",&x,&y);
        addedge(x,y);
    }
    for (int i=2;i<=n;i+=2)
    {
        g[i]=1;
        for (int j=i-1;j>=1;j-=2) g[i]=(LL)g[i]*j%MOD;
    }
    dfs(1,0);
    int ans=0;
    for (int i=2;i<=n;i+=2) (ans+=(LL)f[1][i]*g[i]%MOD)%=MOD;
    printf("%d\n",ans);
    return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_33229466/article/details/82261499