代码源 动归中级 树上背包

树上背包1

http://oj.daimayuan.top/course/8/problem/269
d p [ u ] [ m ] dp[u][m] dp[u][m] 为询问的答案,每次新算出一个儿子就枚举当前的背包容量和儿子的背包容量去合并成新的背包。题目求的是包含 u u u 的连通块,所以初始时 d p [ u ] [ 0 ] dp[u][0] dp[u][0] 应为负无穷,因为不能不选 u u u ,算完 u u u 的答案后应将 d p [ u ] [ 0 ] dp[u][0] dp[u][0] 0 0 0 ,因为算其父节点的时候可以不选 u u u 子树。

#include <bits/stdc++.h>

using namespace std;

typedef long long ll;

const int N = 2e3 + 10;

int n, q, a[N], f[N][N], sz[N], tmp[N];
vector<int> g[N];

void dfs(int u) {
    sz[u] = 1;
    f[u][1] = a[u];
    for (auto v : g[u]) {
        dfs(v);
        for (int i = 0; i <= sz[u] + sz[v]; ++i) tmp[i] = -235802127;
        /*
        这个二重循环在枚举两个背包的各自的所有可能的容量去合并,实际上
        等价于两个子树内的两两点对算一个贡献,而已经被算过的点对不会再次被计算,
        那么从宏观看,最终也就是枚举了整个树上不重复的点对,也就只有 n 方个,所以复杂度正确
        */  
        for (int i = 0; i <= sz[u]; ++i) {
            for (int j = 0; j <= sz[v]; ++j) {
                tmp[i + j] = max(tmp[i + j], f[u][i] + f[v][j]);
            }
        }
        for (int i = 0; i <= sz[u] + sz[v]; ++i) {
            f[u][i] = tmp[i];
        }
        sz[u] += sz[v];
    }
    f[u][0] = 0;
}

int main() {
#ifndef ONLINE_JUDGE
    freopen("in.txt", "r", stdin);
    freopen("out.txt", "w", stdout);
#endif
    memset(f, -0xf, sizeof f);
    scanf("%d%d", &n, &q);
    for (int i = 2; i <= n; ++i) {
        int fa; scanf("%d", &fa);
        g[fa].push_back(i);
    }
    for (int i = 1; i <= n; ++i) {
        scanf("%d", &a[i]);
    }
    dfs(1);
    while(q--) {
        int u, m;
        scanf("%d%d", &u, &m);
        printf("%d\n", f[u][m]);
    }
}

树上背包2

http://oj.daimayuan.top/course/8/problem/270
相比于 1 1 1 来说, n n n 的范围增大, m m m 的范围减小,我们可以在枚举背包容量的时候有 m m m 的上限,这样复杂度就保证了是 O ( n ∗ m ) O(n*m) O(nm)

#include <bits/stdc++.h>

using namespace std;

typedef long long ll;

const int N = 5e4 + 5;
const int M = 1e2 + 5;

int n, q, a[N], f[N][M], sz[N], tmp[M];
vector<int> g[N];

void dfs(int u) {
    sz[u] = 1;
    f[u][1] = a[u];
    for (auto v : g[u]) {
        dfs(v);
        for (int i = 0; i <= min(sz[u] + sz[v], 100); ++i) tmp[i] = -235802127;
        /*
        下列二重循环的整体复杂度为 n*m
        */  
        for (int i = 0; i <= sz[u]; ++i) {
            for (int j = 0; j <= sz[v] && i + j <= 100; ++j) {
                tmp[i + j] = max(tmp[i + j], f[u][i] + f[v][j]);
            }
        }
        for (int i = 0; i <= min(sz[u] + sz[v], 100); ++i) {
            f[u][i] = tmp[i];
        }
        sz[u] += sz[v];
    }
    f[u][0] = 0;
}

int main() {
#ifndef ONLINE_JUDGE
    freopen("in.txt", "r", stdin);
    freopen("out.txt", "w", stdout);
#endif
    memset(f, -0xf, sizeof f);
    scanf("%d%d", &n, &q);
    for (int i = 2; i <= n; ++i) {
        int fa; scanf("%d", &fa);
        g[fa].push_back(i);
    }
    for (int i = 1; i <= n; ++i) {
        scanf("%d", &a[i]);
    }
    dfs(1);
    while(q--) {
        int u, m;
        scanf("%d%d", &u, &m);
        printf("%d\n", f[u][m]);
    }
}

树上背包3

http://oj.daimayuan.top/course/8/problem/271
这题每个点的权重不是 1 1 1 了,所以用树上背包 2 2 2 的方法合并两个背包会达到 O ( n × m 2 ) O(n \times m^2) O(n×m2) 的复杂度。这里设 d p [ i ] [ j ] dp[i][j] dp[i][j] 为考虑 d f s dfs dfs 序在 [ i , n ] [i,n] [i,n] 的节点,选出容量为 j j j 的最大价值,且选出来的点保证不存在某个点被选了,其在 [ i , n ] [i,n] [i,n] 内的祖先没有被选。

这里设 d f s dfs dfs 序为 i i i 的子树大小为 s z [ i ] sz[i] sz[i] ,则若不选 i i i 点,则 i + s z [ i ] − 1 i+sz[i]-1 i+sz[i]1 的点都不能选,则此时的情况等价于 d p [ i + s z [ i ] ] [ j ] dp[i+sz[i]][j] dp[i+sz[i]][j] 。倘若选择 i i i ,那么对于 d p [ i + 1 ] dp[i+1] dp[i+1] 的所有状态来说就是多了一个必选的点,而多选一个点对 d p dp dp 数组含义中限制条件是没有任何影响的,所以就等价于 d p [ i + 1 ] [ j − w [ i ] ] + a [ i ] dp[i+1][j-w[i]]+a[i] dp[i+1][jw[i]]+a[i] ,最终也就是:
d p [ i ] [ j ] = m a x ( d p [ i + s z [ i ] ] [ j ] , d p [ i + 1 ] [ j − w [ i ] ] + a [ i ] ) dp[i][j] = max(dp[i+sz[i]][j],dp[i+1][j-w[i]]+a[i]) dp[i][j]=max(dp[i+sz[i]][j],dp[i+1][jw[i]]+a[i])
后续遍历是因为 d f s dfs dfs 序为 n n n 肯定是个叶子,所以叶子的状态是可以直接求得的,所以可以不断往前推,最终得到正确答案。

#include <bits/stdc++.h>

using namespace std;

typedef long long ll;

const int N = 1e4 + 5;
const int M = 1e4 + 5;
const int INF = -235802127;

int n, m, a[N], w[N], f[N][M], sz[N], dfn[N], rk[N], tot;
vector<int> g[N];

void dfs(int u) {
    ++tot; dfn[u] = tot;
    rk[dfn[u]] = u;
    sz[dfn[u]] = 1;
    for (auto v : g[u]) {
        dfs(v);
        sz[dfn[u]] += sz[dfn[v]];
    }
}

int main() {
#ifndef ONLINE_JUDGE
    freopen("in.txt", "r", stdin);
    freopen("out.txt", "w", stdout);
#endif
    memset(f, -0xf, sizeof f);
    scanf("%d%d", &n, &m);
    for (int i = 2; i <= n; ++i) {
        int fa; scanf("%d", &fa);
        g[fa].push_back(i);
    }
    for (int i = 1; i <= n; ++i) {
        scanf("%d", &a[i]);
    }
    for (int i = 1; i <= n; ++i) {
        scanf("%d", &w[i]);
    }
    dfs(1);
    f[n + 1][0] = 0;
    for (int i = n; i >= 1; --i) {
        for (int j = 0; j <= m; ++j) {
            f[i][j] = f[i + sz[i]][j];
            if (j - w[rk[i]] >= 0 && f[i + 1][j - w[rk[i]]] != INF) {
                f[i][j] = max(f[i][j], f[i + 1][j - w[rk[i]]] + a[rk[i]]);
            }
        }
    }
    for (int i = 0; i <= m; ++i) {
        printf("%d\n", f[1][i] == INF ? 0 : f[1][i]);
    }
}
  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值