【动态规划】树上背包

https://codeforces.com/contest/1281/problem/F

题意:每个顶点有两个权值b和w。最多3000个节点的树,分成恰好m个非空的连通块,使得尽可能多的连通块满足w的和严格大于b的和。

树上背包的套路题,这里背包 \(dp[i][j]\) 表示以i为根的子树,已经划分了 \(j\) 个连通块,包括根节点在内的最后一个连通块没有划分,能取到的最优值。

这一题最优值看起来有两维,但是事实上是一个pair,假如在子树中已经取得了x个连通块“优势区”,那么即使子树中包含根节点在内的是负无穷的“暂时优势”,都比x-1个优势区和正无穷的暂时优势好(因为转移出x个连通块还会额外划分一个连通块,并不是转移到同一个值)。

注意树上背包的siz是最后才加进循环里。

在计算过程中,尚未计算完成的u子树中的dp,不能急着把根节点划分出新的块,要在计算完成后统一加上去。


const int MAXN = 3000 + 10;

int n, m;
vector<int> G[MAXN];
int w[MAXN], b[MAXN];

int siz[MAXN];
vector<pil> dp[MAXN];

pil add(pil A, pil B) {
    A.first += B.first;
    A.second += B.second;
    return A;
}

void dfs(int u, int p) {
    siz[u] = 1;
    dp[u] = vector<pil>(siz[u] + 1, pil(-INF, -LINF));
    dp[u][0] = pil(0, w[u] - b[u]);
    vector<pil> tmp;
    for (int v : G[u]) {
        if (p == v)
            continue;
        dfs(v, u);
        tmp = dp[u];
        dp[u] = vector<pil>(siz[u] + siz[v] + 1, pil(-INF, -LINF));
        for (int i = 0; i <= siz[u] && i <= m; ++i) {
            for (int j = 0; j <= siz[v] && i + j <= m; ++j)
                cmax(dp[u][i + j], add(tmp[i], dp[v][j]));
        }
        siz[u] += siz[v];
    }
    for (int i = min(siz[u] - 1, m - 1); i >= 0; --i)
        cmax(dp[u][i + 1], pil(dp[u][i].first + (dp[u][i].second > 0), 0LL));
}

https://codeforces.com/gym/102992/problem/M

打掉一个怪兽要先打掉他的父亲,打一个怪兽的消耗是这个怪兽的hp和其存活的儿子们的hp的和。事先使用i次激光能打到的最小消耗。

const int MAXN = 2000 + 10;

int n;
vector<int> G[MAXN];
int a[MAXN];

int siz[MAXN];
vector<ll> dp[MAXN][2];

void dfs(int u, int p) {
    siz[u] = 1;
    dp[u][0] = vector<ll>(siz[u] + 1, LINF);
    dp[u][1] = vector<ll>(siz[u] + 1, LINF);
    dp[u][0][0] = a[u], dp[u][1][1] = 0;
    vector<ll> tmp[2];
    for (int v : G[u]) {
        if (v == p)
            continue;
        dfs(v, u);
        tmp[0] = dp[u][0], tmp[1] = dp[u][1];
        dp[u][0] = vector<ll>(siz[u] + siz[v] + 1, LINF);
        dp[u][1] = vector<ll>(siz[u] + siz[v] + 1, LINF);
        for (int i = 0; i <= siz[u]; ++i) {
            for (int j = 0; j <= siz[v]; ++j) {
                cmin(dp[u][0][i + j], tmp[0][i] + min(dp[v][0][j] + a[v], dp[v][1][j]));
                cmin(dp[u][1][i + j], tmp[1][i] + min(dp[v][0][j], dp[v][1][j]));
            }
        }
        siz[u] += siz[v];
    }
}

https://codeforces.com/contest/815/problem/C

dp[u][0][i]:在u子树中,不使用优惠券购买i个物品的最小价格
dp[u][1][i]:在u子树中,使用优惠券购买i个物品的最小价格

const int MAXN = 5000 + 10;

int n;
vector<int> G[MAXN];
int c[MAXN], d[MAXN];

int siz[MAXN];
vector<int> dp[MAXN][2];

void dfs(int u, int p) {
    siz[u] = 1;
    dp[u][0] = vector<int>(siz[u] + 1, INF);
    dp[u][1] = vector<int>(siz[u] + 1, INF);
    dp[u][0][0] = 0, dp[u][0][1] = c[u], dp[u][1][1] = d[u];
    vector<int> tmp[2];
    for (int v : G[u]) {
        if (v == p)
            continue;
        dfs(v, u);
        tmp[0] = dp[u][0], tmp[1] = dp[u][1];
        dp[u][0] = vector<int>(siz[u] + siz[v] + 1, INF);
        dp[u][1] = vector<int>(siz[u] + siz[v] + 1, INF);
        for (int i = 0; i <= siz[u]; ++i) {
            for (int j = 0; j <= siz[v]; ++j) {
                cmin(dp[u][0][i + j], tmp[0][i] + dp[v][0][j]);
                cmin(dp[u][1][i + j], tmp[1][i] + min(dp[v][0][j], dp[v][1][j]));
            }
        }
        siz[u] += siz[v];
    }
}

vector写法空间复杂度会节省非常多(最坏情况仍可以节省一半),由于空间压缩所节省的cache也会使得运行速度提升。

posted @ 2021-03-02 21:32  purinliang  阅读(183)  评论(0)    收藏  举报