1 条题解
-
0
自动搬运
来自洛谷,原作者为

Rainybunny
/ 我是否能成为谁 关于大海的形容 /搬运于
2025-08-24 22:39:26,当前版本为作者最后更新于2022-02-12 21:23:58,作者可能在搬运后再次修改,您可在原文处查看最新版自动搬运只会搬运当前题目点赞数最高的题解,您可前往洛谷题解查看更多
以下是正文
Link. (It's empty temporarily.)
给定一棵含有 个结点的树,点有点权。现进行 次操作,每次操作形如:
- 修改操作 给定 ,对于所有在路径 上,或者存在一个邻接点在路径 上的结点 ,将其点权变为 。
- 询问操作 给定 ,从 出发沿树边走向 ,走到结点 时: 将 的点权加入队列 ; 按标号从小到大枚举 的邻接点 ,若 不在路径上,将 的点权加入队列 ; 最后,走向路径上的下一个点。求出 的最大可空子段和。
。
Subtask 1 按题意模拟,注意开
long long,大家应该都有这档√Subtask 2 线段树板题。反正正解也要打线段树,所以这是非常划算的部分分,应该也不难拿叭。
Subtask 3 在每个结点处拿一些东西(例如线段树)维护儿子,应该对正解有些许启发;当然,也能直接离线处理。
Subtask 4 & Subtask 5 献给差不多想到解法但觉得修改 / 询问操作特别毒瘤的选手,当然如果确实有特殊解法也是好事 awa。由于这两档已经向正解靠拢了,所以分值给高一些 w。
Subtask 6 相当于赋值与求和。献给大概会维护但是不会处理结点顺序
或者懒得写分讨的选手,有特殊解法……也是好事。(Subtask 7 献给高高兴兴写完发现事情并不简单(?)的选手(比如我),从某种意义上也算正解卡常的保底档。拿到这档几乎就会正解啦。
Subtask 8 献给正解。恭喜拿到这档的选手!因为自己的代码很冗长,也想学习一下各位的实现呢 www。
本题 idea 极其昂贵,它出自 「NOI 2021」轻重边。当然这道题的诞生意味这我在 NOI 2021 成为了尸体。(
先从「NOI 2021」轻重边 这道题入手讨论。我们可以把题目抽象为,给定一棵树,支持两种操作:
- 毛毛虫赋值;
- 路径和查询。
其中毛毛虫正如本题修改和查询中涉及的树结构。这里给出形式化定义:
毛毛虫是一个树上点集,由一条树上路径 描述。毛毛虫 表示路径 中的以及与 邻接的所有结点构成的点集。此时,称 为 的虫身, 为 的虫足。
自然地,毛毛虫和路径有诸多相似之处,那么可以想到用树上路径维护的一贯做法——树链剖分维护毛毛虫。可惜,直接树剖全然无从下手。所以,有一个蠢货(特指我)在赛场上磨蹭半天 DIY 出了一个“毛毛虫剖分”。对于想要进行毛毛虫维护的树,毛毛虫剖分用如下方式对其重标号:
-
首先重链剖分,求出重链相关信息。
-
从树根开始递归标号。若现在递归到 :
- 若 未被标号,则按顺序为其标号;
- 若 是重链头,遍历这条重链,按顺序为不在重链上但邻接这条重链的结点标号;
- 若 有重儿子,先递归重儿子;最后递归 的其他儿子。
考察这个妙妙标号方法的性质:
-
对于重链,除链头外,结点标号连续。
-
对于任意结点,它的轻儿子标号连续。
-
对于重链,不在这条重链但与其邻接的结点标号连续。
可见,利用这三个性质,毛毛虫剖分能够同时支持:
- 树上路径修改 / 查询;
- 树上毛毛虫修改 / 查询;
- 子树修改 / 查询。
时间复杂度基于标号后选用的数据维护方法,总的来说,该方法不弱于传统的重链剖分。当然,标号复杂的代价是常数因子,不过这种算法确实是能够通过 NOI 那题的。
什么鬼标题啊这是,不能用分割线吗。用上文的毛毛虫剖分,我们似乎能够轻松解决这道题啦?
可惜,这种剖分方法将毛毛虫的结构乱序化,我们就算可以分别得到虫身和虫足的点集,却无法用同样优秀的时间去维护或修改顺序访问毛毛虫得到的序列。
受上文“毛毛虫剖分 1.0”的启发,我们立马弄一个“毛毛虫剖分 2.0”的重标号方法:
- 首先重链剖分,求出重链相关信息。
- 先为树根标号,然后从树根开始递归标号。若现在递归到结点 :
- 若 是重链头,从 出发遍历这条重链,若现在走到结点 :
- 若 不是重链头,为它标号;
- 顺序遍历 的轻儿子,为它们标号;
- 若 还有重儿子,则继续向重儿子遍历。
- 忽略这条重链,直接以任意顺序递归所有邻接于重链的结点。
- 若 是重链头,从 出发遍历这条重链,若现在走到结点 :
可见,毛毛虫剖分 2.0 无法支持路径修改 / 访问,但却支持顺序修改 / 访问毛毛虫。我们可以用线段树维护重标号后序列的最大子段和,然后爬重链进行修改和询问。
想一想对于一条垂直的链(端点存在祖先-后代关系)在这种标号方法下得到的序列信息是:从上到下遍历链,先加入链上结点,然后顺序加入链外儿子。这对应了 subtask 7 的 分。
但是!事情确实并不简单,正常情况下,我们做树剖询问需要两个路径端点各自爬到 LCA,然后将其中一个结点爬到的答案反序,最后将两个答案拼在一起。而这种编号方法反序的结果是:从下到上遍历链,先逆序加入链外儿子,后加入链上结点,这不符合题目要求的顺序!
因此,我们还需要设计一个与“毛毛虫剖分 2.0”对应的“毛毛虫剖分 2.1”:
- 首先重链剖分,求出重链相关信息。
- 先为树根标号,然后从树根开始递归标号。若现在递归到结点 :
- 若 是重链头,从 出发遍历这条重链,若现在走到结点 :
- 逆序遍历 的轻儿子,为它们标号;
- 若 不是重链头,为它标号;
- 若 还有重儿子,则继续向重儿子遍历。
- 忽略这条重链,直接以任意顺序递归所有邻接于重链的结点。
- 若 是重链头,从 出发遍历这条重链,若现在走到结点 :
在“毛毛虫剖分 2.1”下,询问垂直树链得到的序列信息是:从上到下遍历链,先逆序加入链外儿子,后加入链上结点。反序得到:从下到上遍历链,先加入链上结点,后顺序加入链外儿子。这样,我们才能用这一信息与另一条链合并。
概括地讲,标算为:用两种标号方法对树重标号,用线段树分别维护序列最大子段和。复杂度为 。还有一大难点是细节,例如重链头与重链头的儿子们的先后顺序;LCA 处删去两个轻儿子贡献,加入 LCA 祖先和 LCA 重儿子贡献的顺序……难以一一罗列,具体可见代码中的处理方法。
本来想加上但是害怕被群殴,所以写在题解里的 hard version:追加两种操作:
-
修改操作 ……
-
询问操作 ……
-
链询问操作 给定 ,询问路径 上点权序列的最大子段和。(需额外维护“毛毛虫剖分 1.0”。)
-
优先级修改操作 每个点 初始有优先级 ,询问操作中遍历儿子时,以优先级升序遍历。给定 ,交换 。(平衡树维护序列,区间位移,究极分讨。)
还是 ,反正我不想写 qwq。
/*+Rainybunny+*/ #include <bits/stdc++.h> #define rep(i, l, r) for (int i = l, rep##i = r; i <= rep##i; ++i) #define per(i, r, l) for (int i = r, per##i = l; i >= per##i; --i) typedef long long LL; #define fi first #define se second inline char fgc() { static char buf[1 << 17], *p = buf, *q = buf; return p == q && (q = buf + fread(p = buf, 1, 1 << 17, stdin), p == q) ? EOF : *p++; } template <typename Tp = int> inline Tp rint() { Tp x = 0, s = fgc(), f = 1; for (; s < '0' || '9' < s; s = fgc()) f = s == '-' ? -f : f; for (; '0' <= s && s <= '9'; s = fgc()) x = x * 10 + (s ^ '0'); return x * f; } template <typename Tp> inline void wint(Tp x) { if (x < 0) putchar('-'), x = -x; if (9 < x) wint(x / 10); putchar(x % 10 ^ '0'); } template <typename Tp> inline Tp imin(const Tp& u, const Tp& v) { return u < v ? u : v; } template <typename Tp> inline Tp imax(const Tp& u, const Tp& v) { return u < v ? v : u; } const int MAXN = 1e5, IINF = 0x3f3f3f3f; int n, ecnt, fa[MAXN + 5], val[MAXN + 5], head[MAXN + 5]; int dep[MAXN + 5], siz[MAXN + 5], son[MAXN + 5]; std::vector<int> adj[MAXN + 5]; int dfc[2], dfn[MAXN + 5][2], top[MAXN + 5], ref[MAXN + 5][2]; int clef[MAXN + 5][2], crig[MAXN + 5][2]; int spos[MAXN + 5][2]; struct Atom { LL sum, lmx, rmx, amx; inline Atom rev() const { return { sum, rmx, lmx, amx }; } inline Atom operator + (const Atom& t) const { return { sum + t.sum, imax(lmx, sum + t.lmx), imax(rmx + t.sum, t.rmx), imax(imax(amx, t.amx), rmx + t.lmx) }; } inline Atom& operator *= (const Atom& t) { return *this = *this + t; } inline Atom& operator += (const Atom& t) { return *this = t + *this; } }; struct SegmentTree { Atom uni[MAXN << 2]; int len[MAXN << 2], tag[MAXN << 2]; inline void pushup(const int u) { uni[u] = uni[u << 1] + uni[u << 1 | 1]; } inline void build(const int u, const int l, const int r, const int id) { len[u] = r - l + 1, tag[u] = IINF; if (l == r) { uni[u].sum = val[ref[l][id]]; uni[u].lmx = uni[u].rmx = uni[u].amx = imax(0, val[ref[l][id]]); return ; } int mid = l + r >> 1; build(u << 1, l, mid, id), build(u << 1 | 1, mid + 1, r, id); pushup(u); } inline void pushas(const int u, const int v) { tag[u] = v, uni[u].sum = 1ll * len[u] * v; uni[u].lmx = uni[u].rmx = uni[u].amx = imax(0ll, uni[u].sum); } inline void pushdn(const int u) { if (tag[u] != IINF) { pushas(u << 1, tag[u]), pushas(u << 1 | 1, tag[u]); tag[u] = IINF; } } inline void assign(const int u, const int l, const int r, const int al, const int ar, const int k) { if (al > ar) return ; if (al <= l && r <= ar) return pushas(u, k); int mid = l + r >> 1; pushdn(u); if (al <= mid) assign(u << 1, l, mid, al, ar, k); if (mid < ar) assign(u << 1 | 1, mid + 1, r, al, ar, k); pushup(u); } inline Atom query(const int u, const int l, const int r, const int ql, const int qr) { if (ql > qr) return { 0, 0, 0, 0 }; if (ql <= l && r <= qr) return uni[u]; int mid = l + r >> 1; pushdn(u); if (qr <= mid) return query(u << 1, l, mid, ql, qr); if (mid < ql) return query(u << 1 | 1, mid + 1, r, ql, qr); return query(u << 1, l, mid, ql, qr) + query(u << 1 | 1, mid + 1, r, ql, qr); } } sgt[2]; inline void init(const int u) { siz[u] = 1, dep[u] = dep[fa[u]] + 1; for (int v: adj[u]) { init(v), siz[u] += siz[v]; if (siz[v] > siz[son[u]]) son[u] = v; } } inline void retopF(const int u) { clef[u][0] = dfc[0] + 1; if (!top[u]) dfn[u][0] = ++dfc[0]; for (int v: adj[u]) { if (v == son[u]) spos[u][0] = dfc[0]; else dfn[v][0] = ++dfc[0]; } crig[u][0] = dfc[0]; if (son[u]) retopF(son[u]); } inline void retopR(const int u) { clef[u][1] = dfc[1] + 1; for (int i = int(adj[u].size()) - 1, v; ~i; --i) { if ((v = adj[u][i]) == son[u]) spos[u][1] = dfc[1]; else dfn[v][1] = ++dfc[1]; } if (!top[u]) dfn[u][1] = ++dfc[1]; crig[u][1] = dfc[1]; if (son[u]) retopR(son[u]); } inline void renum(const int u, const int tp) { top[u] = tp; if (u == tp) retopF(u), retopR(u); if (son[u]) renum(son[u], tp); for (int v: adj[u]) if (v != son[u]) renum(v, v); } inline int lca(int u, int v) { while (top[u] != top[v]) { if (dep[top[u]] < dep[top[v]]) v = fa[top[v]]; else u = fa[top[u]]; } return dep[u] < dep[v] ? u : v; } inline void assign(int u, int v, const int k, const int id) { while (top[u] != top[v]) { if (dep[top[u]] < dep[top[v]]) u ^= v ^= u ^= v; sgt[id].assign(1, 1, n, clef[top[u]][id], crig[u][id], k); if (son[u]) { sgt[id].assign(1, 1, n, dfn[son[u]][id], dfn[son[u]][id], k); } u = top[u]; sgt[id].assign(1, 1, n, dfn[u][id], dfn[u][id], k); u = fa[u]; } if (dep[u] < dep[v]) u ^= v ^= u ^= v; sgt[id].assign(1, 1, n, clef[v][id], crig[u][id], k); if (son[u]) sgt[id].assign(1, 1, n, dfn[son[u]][id], dfn[son[u]][id], k); if (v == top[v]) sgt[id].assign(1, 1, n, dfn[v][id], dfn[v][id], k); if (fa[v]) sgt[id].assign(1, 1, n, dfn[fa[v]][id], dfn[fa[v]][id], k); } inline void append(Atom& res, const int u, const int p, const int id) { int l = clef[u][id]; if (dfn[p][id] < spos[u][id]) { res += sgt[id].query(1, 1, n, spos[u][id] + 1, crig[u][id]); res += sgt[id].query(1, 1, n, dfn[son[u]][id], dfn[son[u]][id]); res += sgt[id].query(1, 1, n, imax(dfn[p][id] + 1, l), spos[u][id]); res += sgt[id].query(1, 1, n, l, dfn[p][id] - 1); } else { res += sgt[id].query(1, 1, n, imax(dfn[p][id] + 1, l), crig[u][id]); res += sgt[id].query(1, 1, n, imax(spos[u][id] + 1, l), dfn[p][id] -1); if (son[u]) res += sgt[id].query(1, 1, n, dfn[son[u]][id], dfn[son[u]][id]); res += sgt[id].query(1, 1, n, l, imin(spos[u][id], dfn[p][id] - 1)); } } inline std::pair<Atom, int> climb(int u, const int tar, const int id) { Atom ret = { 0, 0, 0, 0 }; int p = 0; while (top[u] != top[tar]) { if (u != top[u]) { append(ret, u, p, id); ret += sgt[id].query(1, 1, n, crig[top[u]][id] + 1, clef[u][id] - 1); u = top[u]; if (!id) { ret += sgt[0].query(1, 1, n, clef[u][0], crig[u][0]); ret += sgt[0].query(1, 1, n, dfn[u][0], dfn[u][0]); } else { ret += sgt[1].query(1, 1, n, dfn[u][1], dfn[u][1]); ret += sgt[1].query(1, 1, n, clef[u][1], crig[u][1]); } } else if (!id) { append(ret, u, p, 0); ret += sgt[0].query(1, 1, n, dfn[u][0], dfn[u][0]); } else { ret += sgt[1].query(1, 1, n, dfn[u][1], dfn[u][1]); append(ret, u, p, 1); } u = fa[p = u]; } if (u != tar) { append(ret, u, p, id); ret += sgt[id].query(1, 1, n, clef[p = son[tar]][id], clef[u][id] - 1); } return { ret, p }; } inline LL query(const int u, const int v) { int w = lca(u, v); auto su(climb(u, w, 1)), sv(climb(v, w, 0)); auto ret(su.fi.rev()); ret *= sgt[0].query(1, 1, n, dfn[w][0], dfn[w][0]); if (fa[w]) ret *= sgt[0].query(1, 1, n, dfn[fa[w]][0], dfn[fa[w]][0]); std::vector<std::pair<int, int> > tmp; if (su.se && su.se != son[w]) tmp.push_back({ dfn[su.se][0], 0 }); if (sv.se && sv.se != son[w]) tmp.push_back({ dfn[sv.se][0], 0 }); if (su.se != son[w] && sv.se != son[w]) tmp.push_back({ spos[w][0], 2 }); tmp.push_back({ crig[w][0], 1 }); std::sort(tmp.begin(), tmp.end()); int las = clef[w][0] + (w != top[w]); for (auto& p: tmp) { ret *= sgt[0].query(1, 1, n, las, p.fi - !p.se); if (p.fi == spos[w][0] && p.se == 2) { ret = ret + sgt[0].query(1, 1, n, dfn[son[w]][0], dfn[son[w]][0]); } las = p.fi + 1; } ret *= sv.fi; return ret.amx; } int main() { rint(), n = rint(); rep (i, 1, n) val[i] = rint(); rep (i, 2, n) adj[fa[i] = rint()].push_back(i); init(1); dfn[1][0] = dfc[0] = dfn[1][1] = dfc[1] = 1; renum(1, 1); rep (i, 1, n) ref[dfn[i][0]][0] = ref[dfn[i][1]][1] = i; sgt[0].build(1, 1, n, 0), sgt[1].build(1, 1, n, 1); for (int q = rint(), op, u, v, k; q--;) { op = rint(), u = rint(), v = rint(); if (!op) k = rint(), assign(u, v, k, 0), assign(u, v, k, 1); else wint(query(u, v)), putchar('\n'); } return 0; }附上数据生成细则:
1. O(n^2) brute-force | 10pts 1.1. 10/10/10 (n=10/q=10/V=[-10,10]) 1.2. 1000/999/100 | op=1 1.3. 999/1000/100 | only op[q]=1 1.4. 1000/999/1000 | u=v. 1.5. 1000/1000/1e9 2. chain | 10pts 2.1. 1000/1000/1e9 2.2. 1e5/(1e5-1)/1e9 | when op=0, |u-v|<10 2.3. (1e5-1)/1e5/10 | when op=0, |u-v|<100 2.4. 1e5/1e5/1000 2.5. 1e5/1e5/1e9 3. in all operations, u=v holds | 10pts 3.1. 1000/1000/1e9 3.2. 1e5/(1e5-1)/10 | fa<=10, u,v are generated in [1,10] with pr=0.5 3.3. (1e5-1)/1e5/100 | fa<=100, u,v are generated in [1,100] with pr=0.5 3.4. 1e5/1e5/1000 | fa=712, u,v=712 with pr=0.412 3.5. 1e5/1e5/1e9 | fa<=1e4 4. no modification | 15pts 4.1. 1000/1000/1e9 4.2. 1e5/(1e5-1)/100 | fa<=1e4 4.3. (1e5-1)/1e5/1000 | fa<=1e4 4.4. 1e5/1e5/1e9 | fa[u] is in [u-9,u) 4.5. 1e5/1e5/100 | `fa[u] = u - 1 - (u & 1)` 5. only one query operation | 15pts 5.1. 1000/1000/1e9 5.2. 1e5/(1e5-1)/10 5.3. (1e5-1)/1e5/100 5.4. 1e5/1e5/1000 | `fa[u] = u - 1 - (u & 1)`, |u-v|<100 5.5. 1e5/1e5/1e9 6. v>=0 | 10pts 6.1. 1000/1000/1e9 6.2. 1e5/(1e5-1)/1e9 6.3. (1e5-1)/1e5/1e9 6.4. 1e5/1e5/1e9 | fa<=10 6.5. 1e5/1e5/1e9 | `fa[u] = u - 1 - (u & 1)` 7. when op=1, node 1 considered as root, u is v's ancestor | 20pts 7.1. 1000/1000/1e9 7.2. 1e5/(1e5-1)/10 | fa=712, v=712 with pr=0.412 7.3. (1e5-1)/1e5/100 | `fa[u] = u - 1 - (u & 1)`, |u-v|<10 for op=0. 7.4. 1e5/1e5/1000 | fa<=100 7.5. 1e5/1e5/1e9 | fa[u] is in [u-127,u) 8. completed solution | 10pts 8.1. 1000/1000/1e9 8.2. 1e5/(1e5-1)/10 | fa=712, u,v=712 with pr=0.412 8.3. (1e5-1)/1e5/100 | `fa[u] = u - 1 - (u & 1)`, |u-v|<10 8.4. 1e5/1e5/1000 | fa<=100 8.5. 1e5/1e5/1e9 | fa[u] is in [u-127,u)若无特殊说明,树形的生成方式为每个点随机选父亲。可以对着看一看自己为什么得分 / 挂分。(
- 1
信息
- ID
- 7443
- 时间
- 2500ms
- 内存
- 512MiB
- 难度
- 7
- 标签
- 递交数
- 0
- 已通过
- 0
- 上传者