【Codeforces 736C】 Ostap and Tree【树形DP】

题意:

问对一棵树染色,初始无色,要求距每个点最近的染色点的距离不超过k

题解:

之前比赛的时候看见这题就懵了,当时以为是排列组合的结论题(……)

看了题解没怎么看明白,之后看了别人的blog,看见了比题解更优的n*k^2的解法,也挺好理解的

dp[i][j]记录i的子树中当前特征黑点距离i的距离为j的方案数(这里说的很不清楚,下面会解释什么是特征黑点

首先我们知道如果只考虑一棵子树,如果这棵子树自身黑点已经能够使整棵树的节点都满足题目中的条件,那么外面节点如何,这棵子树中的节点肯定是满足条件的,然后考虑这棵子树对外界的影响,很容易知道我们只需要记录距离根节点最近的黑点深度即可

然后考虑一棵子树,这棵子树中的黑点无法满足自身要求,那么我们需要记录的所有分支中距离根最近的黑点的最远值,因为这个黑点是无法使从这个黑点到根的所有节点都满足要求的,所以需要记录其深度,让外部黑点来满足他

说下初始化,

1.根是黑点(dp[u][0]=1)

2.该子树中无黑点(dp[u][k+1]=1)

如果该节点是一个叶子节点,dp[u][k+1]的含义是,该节点的儿子已经涂色完毕了,只要外面的能够u的颜色涂上就行了

如果该节点不是一个叶子节点,含义也差不多,在合并第一个儿子的时候,如果该儿子能使根满足条件,那么当前集合就合法了,赋值到min(k+1,j)中去,如果该儿子不能使根满足条件,赋值到max(k+1,j+1)中去,然后接下来的合并过程就和下面描述的一样了

和所有dp、树形dp一样接下来是最重要的转移过程

对于答案的合并dp[u][i]和dp[v][j](v是u的儿子)

如果i+j<=2*k,那么f[min(i,j+1)]+=dp[u][i]*dp[v][j]

如果i+j>2*k,那么f[max(i,j+1)]+=dp[u][i]*dp[v][j]

那么最后答案就是

#include<set>
#include<map>
#include<cmath>
#include<stack>
#include<queue>
#include<bitset>
#include<vector>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
#define PB push_back
#define MP make_pair
#define ll long long
#define MS(a,b) memset(a,b,sizeof(a))
#define LL (rt<<1)
#define RR (rt<<1|1)
#define lson l,mid,LL
#define rson mid+1,r,RR
#define pii pair<int,int>
#define pll pair<ll,ll>
#define lb(x) (x&(-x))
void In(){freopen("in.in","r",stdin);}
void Out(){freopen("out.out","w",stdout);}
const int N=1e2+10;
const int M=3e5+10;
const int Mbit=1e6+10;
const int inf=0x3f3f3f3f;
const ll mod=1e9+7;
vector<int>G[N];
ll dp[N][45],f[45];
int n,k;
void dfs(int u,int fa)
{
    dp[u][0]=dp[u][k+1]=1;
    for(auto v:G[u]){
        if(v==fa)continue;
        dfs(v,u);
        for(int i=0;i<=2*k;i++)f[i]=0;
        for(int i=0;i<=2*k;i++){
            for(int j=0;j<=2*k;j++){
                if(i+j<=2*k)
                    (f[min(i,j+1)]+=dp[u][i]*dp[v][j])%=mod;
                else
                    (f[max(i,j+1)]+=dp[u][i]*dp[v][j])%=mod;
            }
        }
        for(int i=0;i<=2*k;i++)dp[u][i]=f[i];
    }
}
int main()
{
    while(~scanf("%d%d",&n,&k)){
        MS(dp,0);
        for(int i=1;i<=n;i++)G[i].clear();
        for(int i=1;i<n;i++){
            int u,v;scanf("%d%d",&u,&v);
            G[u].PB(v);
            G[v].PB(u);
        }
        dfs(1,-1);
        ll ans=0;
        for(int i=0;i<=k;i++)
            (ans+=dp[1][i])%=mod;
        cout<<ans<<endl;
    }
    return 0;
}



  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值