[题解] P6847 [CEOI2019] Magic Tree
Yansuan_HCl · · 题解
写篇题解纪念一下我 顺便抢了个最优解。
题意简述
每天你可以砍下树上的某些边,然后当天与
1 节点不再联通的果实如果恰好在当天成熟,则能够收获该果实的果汁。求最大能收获的果汁量。
题解
首先需要想到一个维度含有时间的暴力 dp.
设
暴力转移可以做到
设
试着直接转移
这样,则可以比较方便地使用数据结构维护。这里使用线段树合并。
对于操作 1,直接合并即可。操作 2 的区间取
还有一种经典做法是,把取
此外,想要时间复杂度是正确的,就要求不能下传标记。只有标记永久化的线段树满足这个条件。这要求信息是可叠加的。标记永久化的区间加线段树满足这个条件,尝试用它维护区间推平。
区间推为
代码
const int N = 100005;
int n, m, k;
// 设 f[u][t] 为在第 t 天割掉 u, u 子树内最大收益。
// 若在第 t 天割掉 u, 则子树内所有割的操作只能在第 [1..t] 天完成
// 施加 u 的限制:
// f[u][d[u]]=w[u]+\sum{g[v][d[u]]}
// g[u][i]=sum{g[u][i]+g[v][i]}
// g[u][d[u]..k] \gets w[u]+\sum{g[v][d[u]]}
struct Node { ll mn, mx, tag; int ls, rs; } tr[N << 6];
#define mid ((l + r) >> 1)
#define LS(p) tr[p].ls
#define RS(p) tr[p].rs
#define TAG(p) tr[p].tag
#define N(p) tr[p].mn
#define X(p) tr[p].mx
int trash[N << 6], tp, ap;
#define alloc() (tp ? trash[tp--] : ++ap);
#define del(p) if (p) { trash[++tp] = p; p = LS(p) = RS(p) = X(p) = TAG(p) = N(p) = 0; }
// 标记永久化线段树
// 支持区间 min max, 区间推平为 0, 区间加
// 加法是典型的可加信息。
#define maintain(p) { \
N(p) = min(N(LS(p)), N(RS(p))) + TAG(p); \
X(p) = max(X(LS(p)), X(RS(p))) + TAG(p); \
}
void merge(int &p, int &q, int l = 1, int r = k) {
if (!p || !q) { p = p | q; return; }
if (l == r) {
TAG(p) += TAG(q);
X(p) = N(p) = TAG(p);
del(q);
return;
}
TAG(p) += TAG(q);
merge(LS(p), LS(q), l, mid);
merge(RS(p), RS(q), mid + 1, r);
del(q);
maintain(p);
}
// [b, e] 删除并加上 v
// 其中 f[e]<=v
void cover(int b, ll v, int &p, int l = 1, int r = k) {
if (!p) p = alloc();
if (b <= l) {
if (X(p) <= v) {
del(LS(p)); del(RS(p));
TAG(p) = X(p) = N(p) = v;
} else {
v -= TAG(p);
if (N(LS(p)) <= v)
cover(b, v, LS(p), l, mid);
if (N(RS(p)) <= v)
cover(b, v, RS(p), mid + 1, r);
maintain(p);
}
return;
}
v -= TAG(p);
if (b <= mid) cover(b, v, LS(p), l, mid);
cover(b, v, RS(p), mid + 1, r);
maintain(p);
}
ll query(int x, int p, int l = 1, int r = k) {
if (!p) return 0;
if (l == r) return TAG(p);
return TAG(p) + ((x <= mid) ? query(x, LS(p), l, mid) : query(x, RS(p), mid + 1, r));
}
int fa[N], root[N];
int d[N]; ll w[N];
int main() {
rd(n); rd(m); rd(k);
U (i, 2, n) rd(fa[i]);
U (i, 1, m) {
int u, td; ll tw;
rd(u); rd(td); rd(tw);
d[u] = td;
w[u] = tw;
}
D (u, n, 2) {
if (d[u]) cover(d[u], w[u] + query(d[u], root[u]), root[u]);
merge(root[fa[u]], root[u]);
}
ll f = 0;
U (i, 1, k) f = max(f, query(i, root[1]));
printf("%lld", f);
}
几种错误解法
贪心
按
如果一个点下面有很多
不含时间的 dp
分为砍下
这样无法处理不摘取
暴力 pushdown
若对一棵满树进行 2 操作,则复杂度退化为
这些错误例我全写过。