【牛客题单】动态规划课程树型dp习题

这篇博客汇总了多个与树型动态规划相关的编程题目,包括黑白树、旅游、树问题等,介绍了如何利用动态规划策略解决树结构问题,如旅行路线优化、树的联通集数量计算、删边方案等,适合提升算法思维和编程能力。
摘要由CSDN通过智能技术生成

【牛客题单】动态规划课程树型dp习题

NC13249 黑白树

大意:

一棵n个点的有根树,1号点为根,相邻的两个节点之间的距离为1。树上每个节点i对应一个值k[i]。每个点都有一个颜色,初始的时候所有点都是白色的。你需要通过一系列操作使得最终每个点变成黑色。每次操作需要选择一个节点i,i必须是白色的,然后i到根的链上(包括节点i与根)所有与节点i距离小于k[i]的点都会变黑,已经是黑的点保持为黑。问最少使用几次操作能把整棵树变黑。

思路:

首先需要明确,处理某个节点的时候,它的子节点一定都全部染成黑色了

dfs的时候传递的是:操作res-1次时,子节点能染色的最大范围,而操作res次时,染色的最大范围则挂在k数组上更新,这样相当于进行了两个数值的传递

所以当子节点传递上来的范围不包括当前节点时,就需要进行染色,但是不一定是染这个节点,只是选择一个能贡献最大染色范围的节点

但是我们不需要考虑到底是染哪个点,只需要把最大的染色范围更新到当前的k数组即可

#include <bits/stdc++.h>

using namespace std;

const int N = 1e6 + 5;
typedef long long LL;
int n,f[N],res=0;
vector<int> mp[N];
int dfs(int now, int fa) {
   
    int w = 0;
    for (int i = 0; i < mp[now].size(); i++) {
   
        int ne = mp[now][i];
        if (ne == fa) continue;
        w = max(w, dfs(ne, now));
    }
    if(w<=0){
   
        res++;
        return f[now] - 1;
    }
    f[fa] = max(f[fa], f[now] - 1);
    return w-1;
}
int main() {
   
    cin >> n;
    for (int i = 2; i <= n; i++) {
   
        int x;
        cin >> x;
        mp[x].push_back(i);
    }
    for (int i = 1; i <= n; i++) cin >> f[i];
    dfs(1, 0);
    cout << res << endl;
    return 0;
}

NC15748 旅游

大意:

旅行地图上有n个城市,它们之间通过n-1条道路联通。
Cwbc和XHRlyb第一天会在s市住宿,并游览与它距离不超过1的所有城市,之后的每天会选择一个城市住宿,然后游览与它距离不超过1的所有城市。他们不想住在一个已经浏览过的城市,又想尽可能多的延长旅行时间。XHRlyb想知道她与Cwbc最多能度过多少天的时光呢?

思路:

最大点独立集裸题

#include <bits/stdc++.h>

using namespace std;

const int N = 1e6 + 5;
typedef long long LL;
int n, s,dp[N][2];
vector<int> mp[N];

void dfs(int now, int fa) {
   
    for (int i = 0; i < mp[now].size(); i++) {
   
        int ne = mp[now][i];
        if (ne == fa) continue;
        dfs(ne, now);
        dp[now][1] += dp[ne][0];
        dp[now][0] += max(dp[ne][0], dp[ne][1]);
    }
    dp[now][1]++;
}

int main() {
   
    cin >> n >> s;
    for (int i = 0; i < n - 1; i++) {
   
        int x, y;
        cin >> x >> y;
        mp[x].push_back(y);
        mp[y].push_back(x);
    }
    dfs(s, 0);
    cout << dp[s][1] << endl;
    return 0;
}

NC19782 Tree

大意:

对于n个节点的一个树中的每一个点,求出包含这个点的点联通集的数量

思路:

换根就要想到把联通集分为两类,一类是这个点为根的子树中的联通集,另一类是不以它为根的

设dp[ i ]为以i为根的子树中包含i这个点的点联通集的数量,ne为i的子节点,那么:

d p [ i ] = ∏ ( d p [ n e ] + 1 ) dp[i]=\prod(dp[ne]+1) dp[i]=(dp[ne]+1)

即每个子节点选(dp[ne])或不选(1),然后相乘

设pre[i]为除了i的子树中包含i号点的联通集的数量,假设res[ i ]为每个点的答案,fa为父节点,那么有:

p r e [ i ] = r e s [ f a ] d p [ i ] + 1 pre[i]=\frac{res[fa]}{dp[i]+1} pre[i]=dp[i]+1res[fa]

因为res[ fa ]代表父节点的答案,也就是所有包含父节点的联通集,可以由父节点以上的联通块再乘上i节点选或不选,

即res[fa] =pre[i] * (dp[i]+1),那么直接除一下得到pre[i]

但是是取模意义下的,所以要求逆元

但是如果dp[i]+1对mod取模为0,那么无法直接求逆元来算pre,此时只需要反向考虑,暴力求一下pre即可

总结一下就是务必要搞清楚各个数组的含义,不然很难推出传递关系。

#include <bits/stdc++.h>

using namespace std;

const int N = 1e6 + 5;
typedef long long LL;

LL qmi(LL a, LL k, LL p) {
   
    LL res = 1 % p;  // res记录答案, 模上p是为了防止k为0,p为1的特殊情况
    while (k) {
     // 只要还有剩下位数
        if (k & 1)
            res =
                (LL)res * a % p;  // 判断最后一位是否为1,如果为1就乘上a,模上p,
                                  // 乘法时可能爆int,所以变成long long
        k >>= 1;  // 右移一位
        a = (LL)a * a %
            p;  // 当前a等于上一次的a平方,取模,平方时可能爆int,所以变成long
                // long
    }
    return res;
}

LL get_inv(LL a, LL p) {
    return 
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值