Plutotree

题目蓝链

Solution

虽然这题加了很多边,但本质上还是一棵树,我们只需要维护一下链上的和与最大值,然后直接树形DP

但不过这道题的DP比较独特,因为一个节点的DP值可以从它的父亲更新过来。那么这个DP是不是就是有后向性了呢?

其实我们只需要DP两遍就可以了,第一遍DP处理出一个点到它的子树内的最优值。第二遍的时候,对于每一个点\(i\)记录一下一个权值\(dp_i - pre_i\)(\(pre_i\)为当前点到根的前缀和),在更新\(i\)号点的时候,我们在它的所有祖先中找一个这个权值最小的点\(j\),然后用\(dp_j - pre_j + pre_i\)来更新当前点就可以了

Code

#include <bits/stdc++.h>

using namespace std;

#define squ(x) ((LL)(x) * (x))
#define debug(...) fprintf(stderr, __VA_ARGS__)

typedef long long LL;
typedef pair<int, int> pii;

inline int read() {
    int sum = 0, fg = 1; char c = getchar();
    for (; !isdigit(c); c = getchar()) if (c == '-') fg = -1;
    for (; isdigit(c); c = getchar()) sum = (sum << 3) + (sum << 1) + (c ^ 0x30);
    return fg * sum;
}

const int maxn = 1e5 + 10;
const int inf = 0x3f3f3f3f;

int n, q;

namespace Tree {

    vector<int> g[maxn];
    int d[maxn], fa[maxn][17], Max[maxn][17];
    int w[maxn], pre[maxn];
    pii dp[maxn];

    void input() {
        for (int i = 2; i <= n; i++) g[read()].push_back(i);
        for (int i = 1; i <= n; i++) w[i] = read();
    }

    void dfs1(int now, int f) {
        dp[now] = (pii){inf, inf}, pre[now] = pre[f] + w[now];
        d[now] = d[f] + 1, fa[now][0] = f, Max[now][0] = max(w[now], w[f]);
        for (int i = 1; i <= 16; i++) {
            fa[now][i] = fa[fa[now][i - 1]][i - 1];
            Max[now][i] = max(Max[now][i - 1], Max[fa[now][i - 1]][i - 1]);
        }
        for (int son : g[now]) {
            dfs1(son, now);
            pii tmp = (pii){dp[son].first + w[now], min(dp[son].second, -w[now])};
            dp[now] = min(dp[now], tmp);
        }
        if (dp[now] == (pii){inf, inf}) dp[now] = (pii){w[now], -w[now]};
    }

    void dfs2(int now, pii pp) {
        pii tmp = (pii){pp.first + pre[now], min(pp.second, -w[now])};
        dp[now] = min(dp[now], tmp);
        tmp = (pii){dp[now].first - pre[now], dp[now].second};
        pp = min(pp, tmp);
        for (int son : g[now]) dfs2(son, pp);
    }

    int lca(int x, int y) {
        if (d[x] < d[y]) swap(x, y);
        for (int i = 16; ~i; i--)
            if (d[fa[x][i]] >= d[y]) x = fa[x][i];
        if (x == y) return x;
        for (int i = 16; ~i; i--)
            if (fa[x][i] != fa[y][i]) x = fa[x][i], y = fa[y][i];
        return fa[x][0];
    }

    int sum(int x, int y) {
        int f = lca(x, y), ff = fa[f][0];
        return pre[x] + pre[y] - pre[f] - pre[ff];
    }

    int get_max(int x, int y) {
        int MAX = max(w[x], w[y]), ff = lca(x, y);
        for (int i = 16; ~i; i--)
            if (d[fa[x][i]] >= d[ff]) MAX = max(MAX, Max[x][i]), x = fa[x][i];
        for (int i = 16; ~i; i--)
            if (d[fa[y][i]] >= d[ff]) MAX = max(MAX, Max[y][i]), y = fa[y][i];
        return MAX;
    }

    void query(int x, int y) {
        pii ans = (pii){sum(x, y), -get_max(x, y)};
        pii t1 = (pii){dp[x].first + pre[y], min(-get_max(y, 1), dp[x].second)};
        pii t2 = (pii){dp[y].first + pre[x], min(-get_max(x, 1), dp[y].second)};
        pii t3 = (pii){dp[x].first + dp[y].first + w[1], min(-w[1], min(dp[x].second, dp[y].second))};
        ans = min(ans, t1), ans = min(ans, t2), ans = min(ans, t3);
        printf("%d %d\n", ans.first, -ans.second);
    }

}

int main() {
    freopen("plutotree.in", "r", stdin);
    freopen("plutotree.out", "w", stdout);

    n = read(), q = read();
    Tree::input();
    Tree::dfs1(1, 0), Tree::dfs2(1, (pii){inf, inf});

    while (q--) {
        int x = read(), y = read();
        Tree::query(x, y);
    }

    return 0;
}

Summary

我是真的菜,一开始一直以为这题不能DP....

这是我第一次接触这种多次更新答案的树形DP题,又学到了一个新的技巧

转载于:https://www.cnblogs.com/xunzhen/p/9692849.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值