Codeforces Round #514 Div2 E. Split the Tree

http://codeforces.com/problemset/problem/1059/E

现场想到了假的 DP,我的贪心能力还是 naive 啊。。。

问题

给一棵树,每个点有一些点权。定义垂直路径为:一条树上路径 \(v_1,v_2,\dots,v_m\),对于 \(i \ge 2\) 都有 \(v_{i-1}\)\(v_i\) 的父亲。

现在要把这棵树划分成若干条垂直路径,使每个节点都被一条垂直路径包含,要求:

  • 每条垂直路径的点个数不超过 \(L\)
  • 点权和不超过 \(S\)

问最少能用多少条路径划分。

题解

好像贪心就行了啊,计算每颗子树的最优解时,根节点可以是新开一条路径,或者使接上一条经过根节点的路径,并且这条路径向上延伸的长度是最长的。正确性。。。显然?

#include <bits/stdc++.h>

using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;

int rint() {
    int n, c, sgn = 0;
    while ((c = getchar()) < '-');
    if (c == '-') n = 0, sgn = 1;
    else n = c - '0';
    while ((c = getchar()) >= '0') n = 10 * n + c - '0';
    return sgn ? -n : n;
}

const int N = 100010;

int n;
int L;
ll S;
int par[N], gp[N][20];
ll gw[N][20];
int w[N];
int up[N];
int top[N];
int dep[N];

int main() {
    scanf("%d %d %lld", &n, &L, &S);
    for (int i = 0; i < n; i++) {
        scanf("%d", &w[i]);
        if (w[i] > S) {
            puts("-1");
            return 0;
        }
    }
    par[0] = -1;
    for (int i = 1; i < n; i++) {
        scanf("%d", &par[i]);
        par[i]--;
    }

    for (int i = 0; i < n; i++) {
        int p = par[i];
        gp[i][0] = p;
        if (p != -1) {
            gw[i][0] = w[p];
            dep[i] = dep[p] + 1;
        } else {
            gw[i][0] = 0;
            dep[i] = 0;
        }
        for (int j = 0; j < 19; j++) {
            if (gp[i][j] == -1) {
                gp[i][j+1] = -1;
            } else {
                gp[i][j+1] = gp[gp[i][j]][j];
                gw[i][j+1] = gw[i][j] + gw[gp[i][j]][j];
            }
        }

        int A = 1;
        ll B = w[i];
        int x = i;
        for (int j = 19; j >= 0; j--) {
            if (gp[x][j] == -1) continue;
            if (A + (1 << j) <= L && B + gw[x][j] <= S) {
                A += (1 << j);
                B += gw[x][j];
                x = gp[x][j];
            }
        }
        assert(x >= 0);
        up[i] = x;
    }

    int ans = 0;
    fill(top, top + n, -1);
    for (int i = n-1; i >= 0; i--) {
        if (top[i] == -1) {
            top[i] = up[i];
            ans++;
        }
        int p = par[i];
        if (dep[top[i]] <= dep[p] && (top[p] == -1 || dep[top[i]] < dep[top[p]])) {
            top[p] = top[i];
        }
    }

    printf("%d\n", ans);

    return 0;
}

总结

这种用 \(p_i < i\) 来表示树的方法好评啊,不用递归了。

猜你喜欢

转载自www.cnblogs.com/hfccccccccccccc/p/9777455.html