ICPC World Finals 2020 ‘S No Problem (树形dp) (k 条不相交路径覆盖最值问题)

博客介绍了如何使用树形分组背包的方法解决用最少成本覆盖一棵树的问题。首先将问题转换为从任意点出发,使每条边被标记两次,得到上界为两倍边权和。然后通过寻找两条不相交的简单路径最大化权值,从而找到最小花费。代码实现中展示了如何运用动态规划和树形分组背包技巧来解决这一问题。

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

TP

题意:

  • 用两条线(可以来回走,可以相交,不要求是简单路径)覆盖一颗树,花费为每条边被覆盖的次数乘边权之和。问覆盖这棵树的最小花费是多少?

思路:

  • 首先转换一下问题,从任意一个点出发,我们一定能每条边经过两次(标记两次)再回到该点。这样花费的上界就是 两倍的边权和。

  • 之后我们再用线(不是题意里的线了)去消除标记,可以发现,经过一条边相当于把该边的标记 -1,一条边的标记至少为 1(不然就不是覆盖这棵树了),这样的线走过的一定是简单路径,如果用两条的话,这两条线一定不会有公共边(否则一条边的标记就会减 2)。

  • 致此问题就转换成,如何用两条不公共的简单路径最大化走过的权值。最后答案就是 两倍边权和 - 该最大化值,也就是我们的最小花费

  • 这个思路也可以拓展为用 k 条不公共的简单路径覆盖一棵树能获得的最大值。具体方法看代码。

写法:

  • 树形分组背包,神中神的定义

摘自知乎题解:dalao

C o d e : Code: Code:

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define mem(a,b) memset(a,b,sizeof a)
#define cinios (ios::sync_with_stdio(false),cin.tie(0),cout.tie(0))

const int N = 1e5 + 10, M = 1e6 + 10, mod = 1e9 + 7;
int n, m, ki;

int h[N], e[M], ne[M], w[M], idx;
void add(int a, int b, int x) {
    e[idx] = b, ne[idx] = h[a], w[idx] = x, h[a] = idx++;
}

static vector<int> dfs(int x, int f, int up) {

    //dp[0] = 0;
    //dp[1] 代表 往 x 结点之上延伸 的最长的 一条线段
    //dp[2] 代表 不往 x 结点之上延伸 的最长的 一条线段
    //dp[3] 代表 往 x 结点之上延伸(当然只能延伸一条) 的两条线段
    //dp[4] 代表 不往 x 结点之上延伸 两条线段

    //以此类推 dp[5] 、dp[6] 

    vector<int> dp(up + 1, 0);

    for (int v = h[x]; ~v; v = ne[v]) {
        int to = e[v]; if (to == f)continue;

        auto sub = dfs(to, x, up);//返回数组类型,方便分组背包

        for (int i = up - 1; i >= 0; i--) { //倒序背包,i 可以为 0,j 为 0 无意义
            for (int j = up - i; j >= 1; j--) {

                //可以发现当且仅当 j 下标为奇数时,对应线段在定义上是要往 to 结点之上延伸的
                //也就是说可以获得 x -> to 这条边的权值

                int ww = dp[i] + sub[j] + (j & 1 ? w[v] : 0);
                dp[i + j] = max(dp[i + j], ww);
            }
        }
    }
    return dp;
}

int main() {
    cinios;

    cin >> n;
    mem(h, -1);

    int K = 2;//两条线覆盖

    int ans = 0;
    for (int i = 1; i < n; i++) {
        int a, b, x;
        cin >> a >> b >> x;
        add(a, b, x), add(b, a, x);
        ans += 2 * x;
    }

    auto res = dfs(1, -1, 2 * K);//两倍维度
    int mx = res[2 * K];

    cout << ans - mx;

    return 0;
}

模块化的代码真的太漂亮了

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值