【容斥原理】【树形DP】Atcoder ARC101 Ribbons on Tree

一道结合树形动态规划和容斥原理的Atcoder题目,通过转化将问题转化为求解特定联通块内的组合数,利用动态规划进行高效计算,其中涉及对子树进行状态转移并处理特殊情况。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

分析:

很不错的树形DP+容斥原理的混合题

不得不说,尽管Atcoder去世了一个半月,但这次题目质量仍然保持得很不错

首先,这题可以运用容斥原理转化一下:定义EE为给出的边集,设FEf(F)f(F)表示:F中的边全部未被覆盖的方案数。

答案就是(1)|F|f(F)∑(−1)|F|f(F)

那么接下来就是求f(F)f(F)

其实这个很显然,考虑在树上去除所有F中的边,那么每次选择的点对,都必须在剩下的图中的同一个联通块内。这样的联通块必定有|F|+1|F|+1个,设其点数分别为n1,n2,n3n|F|+1n1,n2,n3…n|F|+1

设一个函数g(n)g(n)表示在n个点中选出点两两一组的方案数。
那么很显然当n为奇数时,g(n)=0g(n)=0
否则g(n)=(n1)(n3)31g(n)=(n−1)∗(n−3)∗……∗3∗1

那么f(F)=g(n1)g(n2)g(n3)g(n|F|+1)f(F)=g(n1)∗g(n2)∗g(n3)……g(n|F|+1)

现在考虑快速地求f(F)f(F),这时树形DP就派上用场了。

定义dp(x,sum)dp(x,sum)表示以x为根的子树中,和x的父亲相连的点有sum个时的方案数。

转移也就很明显了,依次遍历每个亲儿子,暴力枚举在这个儿子的子树中选kk个点的方案数。

当sum=0时,就相当于和父亲的边在F中,这种情况的转移比较特殊,因为此时相当于一个联通块被考虑完了,所以要枚举所有可能的联通块大小,并且由于F多了这一条边,所以要全部取相反数,dp(x,0)=dp(x,sum)f(sum)dp(x,0)=−∑dp(x,sum′)∗f(sum′)

#include<cstdio>
#include<cstring>
#include<cmath>
#include<vector>
#define SF scanf
#define PF printf
#define MAXN 5010
#define MOD 1000000007
using namespace std;
typedef long long ll;
ll dp[MAXN][MAXN],f[MAXN],t[MAXN];
int siz[MAXN];
vector<int> a[MAXN];
void dfs(int x,int fa){
    siz[x]=1;
    dp[x][1]=1;
    for(int i=0;i<a[x].size();i++){
        int u=a[x][i];
        if(u==fa)
            continue;
        dfs(u,x);
        for(int j=0;j<=siz[x];j++){
            t[j]=dp[x][j];
            dp[x][j]=0;
        }
        for(int j=0;j<=siz[x];j++)
            for(int k=0;k<=siz[u];k++)
                (dp[x][j+k]+=t[j]*dp[u][k]%MOD)%=MOD;
        siz[x]+=siz[u];
    }
    for(int i=0;i<=siz[x];i+=2)
        dp[x][0]=(dp[x][0]-dp[x][i]*f[i]%MOD+MOD)%MOD;
}
int n,u,v;
int main(){
    SF("%d",&n);
    for(int i=1;i<n;i++){
        SF("%d%d",&u,&v);
        a[u].push_back(v);
        a[v].push_back(u);
    }   
    f[0]=1;
    for(int i=2;i<=n;i+=2)
        f[i]=f[i-2]*(i-1)%MOD;
    dfs(1,0);
    PF("%lld",MOD-dp[1][0]);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值