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也会使得运行速度提升。