防剧透防剧透防剧透防剧透防剧透防剧透防剧透防剧透防剧透防剧透防剧透防剧透防剧透防剧透防剧透防剧透防剧透防剧透防剧透防剧透防剧透防剧透防剧透防剧透防剧透防剧透防剧透防剧透防剧透防剧透防剧透防剧透防剧透防剧透防剧透防剧透防剧透防剧透防剧透防剧透防剧透防剧透防剧透防剧透防剧透防剧透防剧透防剧透防剧透防剧透阅读全文

第一次写黑题题解,特别纪念一下。

题目传送门

比赛传送门

前言

偷偷的说(别被他本人发现了已经被发现了):今年是老师Rainybunny出这道题的第 \(3\) 年,当他给我讲树链剖分的时,语言略带神秘,但不禁露出些许自豪的说,这道题是他出的。

但,即使是他出的,我也要吐槽一下:这什么屎山代码?

有大佬用虚实链剖分切了,鄙人不才,所以写一个大家都可以理解的题解供大家参考。

题目大意

给定 \(n\) 个结点的树,有 \(q\) 次操作,如下:

  • 修改:对于在路径 \((u,v)\) 上的节点或在路径上的邻接点 \(x\),将 \(x\) 的点权变为 \(k\)。
  • 查询:对于在路径 \((u,v)\) 上的节点 \(x\):

    1.将 \(x\) 的点权加入 \(S\);

    2.按标号升序枚举 \(x\) 的不在路径上的邻接点 \(y\),将 \(y\) 的点权加入 \(S\)。求 \(S\) 的最大可空子段和。

看似很简单。

引入

大家都知道「NOI 2021」轻重边这道题吧,这道题是这样的:

每条边一开始都是轻边。

有两种操作:

  • 修改:将 \((u,v)\) 路径上的点的邻接点变成轻边,再将 \((u,v)\) 路径上的边变成重边。
  • 查询:\((u,v)\) 上一共有多少条重边。

我们发现了一个没有什么用的性质:每次修改的部分很像一条毛毛虫。

显然,我们要将边变成点,用一个点表示一条边,可以说这样做是很疯狂的,非常恐怖。

不妨换一个思路,一条边是重边,当且仅当:它被次修改的路径包含,并且在此次修改后, 它的两个端点都没有被其他修改路径包含。

换句话说:最后一次涉及这条边的两个端点的修改是同一个修改。

如此,我们便解决了这道题,但是,对毛毛虫的修改和对路径查询,真的不能做吗?

答案是:能!

思路

回忆树链剖分的基本逻辑:将需要维护的信息(树上路径)通过重编号(dfn)实现到区间上。现在,我们更关心所谓的毛毛虫的信息,那么就需要设计一种将毛毛虫通过重编号转化到区间的算法。

我们先定义一下毛毛虫的结构:对于数 \(Tree=(V,E)\) 和树上一条路径的点集 \(P\),定义毛毛虫为:

\[C=P\cup\underset{x\in P}{\bigcup}\{y\mid(x,y)\in E\}
\]

同时,我们亲切的称 \(P\) 为 \(C\) 的毛毛虫虫身,\(C\setminus P\) 为 \(C\) 的虫足。

这样,原题的两种操作可以用一下三种操作组合而成:

  1. 虫足点权赋值(为 \(0\),除了 \(LCA\) 的父亲);
  2. 虫身赋值(为 \(1\),除了 \(LCA\));
  3. 虫身点权和查询(减去 \(LCA\) 贡献)。

重点:解决了定义,但如何剖分?

汪老师在课堂上说这个是毛毛虫剖分。

首先,为什么我们要用重链?因为我们跳整个链,从 \(S_u\gets2S_u\),我们只有用重链剖分才能保证我的时间复杂度。

但是,我们要再重链剖分的基础上改进一下,在其原有的性质的基础上增加必要的性质。

对于本题来说,重编号方法不难构造。先为树根编号,接着从树根所在的重链出发,依次进行:

  1. 为除重链头 \(top\) 以外的重链结点依次编号;
  2. 依次枚举重链结点,为它们的轻儿子依次编号(这些儿子是其他重链的 \(top\));
  3. 任意顺序递归 \(2.\) 中的轻儿子。



在这幅图中,黄色箭头表示将要递归的子树;橙色边是重边;蓝色的框是重儿子;绿色框是重链两边的轻儿子。

可以看出,毛毛虫剖分的重编号的性质:

  • 对于重链,除 \(top\) 外的结点编号连续;
  • 对于任意结点,其轻儿子编号连续;
  • 对于重链毛毛虫,除 \(top\) 的父亲外所有虫足编号连续。

所以,毛毛虫剖分可以很方便的维护毛毛虫信息。并且,他能顺便维护重链剖分的所有信息,还能维护子树的所有信息(子树最多剖分为三部分:重链区间、邻接轻点区间、邻接轻子树区间)。

时间复杂度为:\(O(q\log^2n)\),与重链剖分完全一样。

屎山代码

知道了思路就很好做了,但是写的时候很难说。

#include <bits/stdc++.h>
#define For(i, l, r) for (int i = l;i <= r; i++)
#define min(a,b) (a)<(b)?(a):(b)
#define max(a,b) (a)<(b)?(b):(a)
#define LL long long
#define fi first
#define se second using namespace std; inline int read() {
int x;
cin >> x;
return x;
} const int N = 1e5 + 10, IINF = 0x3f3f3f3f;
int n, ecnt, fa[N], val[N], head[N];
int dep[N], siz[N], son[N];
vector<int> g[N]; int dfc[2], dfn[N][2], top[N], rnk[N][2];
int clef[N][2], crig[N][2];
int spos[N][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, max(lmx, sum + t.lmx), max(rmx + t.sum, t.rmx), max(max(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[N << 2];
int len[N << 2], tag[N << 2];
inline void pushup(const int u) {
uni[u] = uni[u << 1] + uni[u << 1 | 1];
}
inline void build(int u, int l, int r, int id) {
len[u] = r - l + 1, tag[u] = IINF;
if (l == r) {
uni[u].sum = val[rnk[l][id]];
uni[u].lmx = uni[u].rmx = uni[u].amx = max(0, val[rnk[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 change(int u, int v) {
tag[u] = v, uni[u].sum = 1ll * len[u] * v;
uni[u].lmx = uni[u].rmx = uni[u].amx = max(0ll, uni[u].sum);
}
inline void pushdown(int u) {
if (tag[u] != IINF) {
change(u << 1, tag[u]), change(u << 1 | 1, tag[u]);
tag[u] = IINF;
}
}
inline void mulity(int u, int l, int r,int al, int ar, int k) {
if (al > ar) return ;
if (al <= l && r <= ar) return change(u, k);
int mid = (l + r) >> 1;
pushdown(u);
if (al <= mid) mulity(u << 1, l, mid, al, ar, k);
if (mid < ar) mulity(u << 1 | 1, mid + 1, r, al, ar, k);
pushup(u);
}
inline Atom query(int u, int l, int r,int ql, int qr) {
if (ql > qr) return { 0, 0, 0, 0 };
if (ql <= l && r <= qr) return uni[u];
int mid = (l + r) >> 1;
pushdown(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);
}
} T[2]; inline void dfs1(int u) {
siz[u] = 1, dep[u] = dep[fa[u]] + 1;
for (int v : g[u]) {
dfs1(v), siz[u] += siz[v];
if (siz[v] > siz[son[u]]) son[u] = v;
}
} inline void retopF(int u) {
clef[u][0] = dfc[0] + 1;
if (!top[u]) dfn[u][0] = ++dfc[0];
for (int v : g[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(int u) {
clef[u][1] = dfc[1] + 1;
for (int i = int(g[u].size()) - 1, v; ~i; --i) {
if ((v = g[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 dfs2(int u, int tp) {
top[u] = tp;
if (u == tp) retopF(u), retopR(u);
if (son[u]) dfs2(son[u], tp);
for (int v : g[u]) if (v != son[u]) dfs2(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 mulity(int u, int v, int k, int id) {
while (top[u] != top[v]) {
if (dep[top[u]] < dep[top[v]]) u ^= v ^= u ^= v;
T[id].mulity(1, 1, n, clef[top[u]][id], crig[u][id], k);
if (son[u]) {
T[id].mulity(1, 1, n, dfn[son[u]][id], dfn[son[u]][id], k);
}
u = top[u];
T[id].mulity(1, 1, n, dfn[u][id], dfn[u][id], k);
u = fa[u];
}
if (dep[u] < dep[v]) u ^= v ^= u ^= v;
T[id].mulity(1, 1, n, clef[v][id], crig[u][id], k);
if (son[u]) T[id].mulity(1, 1, n, dfn[son[u]][id], dfn[son[u]][id], k);
if (v == top[v]) T[id].mulity(1, 1, n, dfn[v][id], dfn[v][id], k);
if (fa[v]) T[id].mulity(1, 1, n, dfn[fa[v]][id], dfn[fa[v]][id], k);
} inline void append(Atom& res, int u, int p,int id) {
int l = clef[u][id];
if (dfn[p][id] < spos[u][id]) {
res += T[id].query(1, 1, n, spos[u][id] + 1, crig[u][id]);
res += T[id].query(1, 1, n, dfn[son[u]][id], dfn[son[u]][id]);
res += T[id].query(1, 1, n, max(dfn[p][id] + 1, l), spos[u][id]);
res += T[id].query(1, 1, n, l, dfn[p][id] - 1);
} else {
res += T[id].query(1, 1, n, max(dfn[p][id] + 1, l), crig[u][id]);
res += T[id].query(1, 1, n, max(spos[u][id] + 1, l), dfn[p][id] - 1);
if (son[u])
res += T[id].query(1, 1, n, dfn[son[u]][id], dfn[son[u]][id]);
res += T[id].query(1, 1, n, l, min(spos[u][id], dfn[p][id] - 1));
}
} inline pair<Atom, int> climb(int u, int tar, 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 += T[id].query(1, 1, n,
crig[top[u]][id] + 1, clef[u][id] - 1);
u = top[u];
if (!id) {
ret += T[0].query(1, 1, n, clef[u][0], crig[u][0]);
ret += T[0].query(1, 1, n, dfn[u][0], dfn[u][0]);
} else {
ret += T[1].query(1, 1, n, dfn[u][1], dfn[u][1]);
ret += T[1].query(1, 1, n, clef[u][1], crig[u][1]);
}
} else if (!id) {
append(ret, u, p, 0);
ret += T[0].query(1, 1, n, dfn[u][0], dfn[u][0]);
} else {
ret += T[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 += T[id].query(1, 1, n, clef[p = son[tar]][id], clef[u][id] - 1);
}
return { ret, p };
} inline LL query(int u, int v) {
int w = lca(u, v);
auto su(climb(u, w, 1)), sv(climb(v, w, 0));
auto ret(su.fi.rev());
ret *= T[0].query(1, 1, n, dfn[w][0], dfn[w][0]);
if (fa[w]) ret *= T[0].query(1, 1, n, dfn[fa[w]][0], dfn[fa[w]][0]);
vector<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 });
sort(tmp.begin(), tmp.end());
int las = clef[w][0] + (w != top[w]);
for (auto& p : tmp) {
ret *= T[0].query(1, 1, n, las, p.fi - !p.se);
if (p.fi == spos[w][0] && p.se == 2) {
ret = ret + T[0].query(1, 1, n, dfn[son[w]][0], dfn[son[w]][0]);
}
las = p.fi + 1;
}
ret *= sv.fi;
return ret.amx;
} signed main() {
cin.tie(nullptr)->sync_with_stdio(false);
read(), n = read();
For (i, 1, n) val[i] = read();
For (i, 2, n) g[fa[i] = read()].push_back(i);
dfs1(1);
dfn[1][0] = dfc[0] = dfn[1][1] = dfc[1] = 1;
dfs2(1, 1);
For (i, 1, n) rnk[dfn[i][0]][0] = rnk[dfn[i][1]][1] = i;
T[0].build(1, 1, n, 0), T[1].build(1, 1, n, 1);
for (int q = read(), op, u, v, k; q--;) {
op = read(), u = read(), v = read();
if (!op) k = read(), mulity(u, v, k, 0), mulity(u, v, k, 1);
else cout << query(u, v) << endl;
}
return 0;
}

[题解] 洛谷 P8479 「GLR-R3」谷雨 题解的更多相关文章

  1. 题解-洛谷P6788 「EZEC-3」四月樱花

    题面 洛谷P6788 「EZEC-3」四月樱花 给定 \(n,p\),求: \[ans=\left(\prod_{x=1}^n\prod_{y|x}\frac{y^{d(y)}}{\prod_{z|y ...

  2. [洛谷P3701]「伪模板」主席树

    题目大意:太暴力了,就不写了,看这儿 题解:对于每个$byx$的人,从源点向人连边,容量为此人的寿命. 对于每个手气君的人,从人向汇点连边,容量为此人的寿命. 对于每个$byx$的人与手气君的人,如果 ...

  3. LOJ 3119: 洛谷 P5400: 「CTS2019 | CTSC2019」随机立方体

    题目传送门:LOJ #3119. 题意简述: 题目说的很清楚了. 题解: 记恰好有 \(i\) 个极大的数的方案数为 \(\mathrm{cnt}[i]\),则答案为 \(\displaystyle\ ...

  4. LOJ 3120: 洛谷 P5401: 「CTS2019 | CTSC2019」珍珠

    题目传送门:LOJ #3120. 题意简述: 称一个长度为 \(n\),元素取值为 \([1,D]\) 的整数序列是合法的,当且仅当其中能够选出至少 \(m\) 对相同元素(不能重复选出元素). 问合 ...

  5. 洛谷 P4710 「物理」平抛运动

    洛谷 P4710 「物理」平抛运动 洛谷传送门 题目描述 小 F 回到班上,面对自己 28 / 110 的物理,感觉非常凉凉.他准备从最基础的力学学起. 如图,一个可以视为质点的小球在点 A(x_0, ...

  6. 洛谷比赛 「EZEC」 Round 4

    洛谷比赛 「EZEC」 Round 4 T1 zrmpaul Loves Array 题目描述 小 Z 有一个下标从 \(1\) 开始并且长度为 \(n\) 的序列,初始时下标为 \(i\) 位置的数 ...

  7. 「GXOI / GZOI2019」简要题解

    「GXOI / GZOI2019」简要题解 LOJ#3083. 「GXOI / GZOI2019」与或和 https://loj.ac/problem/3083 题意:求一个矩阵的所有子矩阵的与和 和 ...

  8. 洛谷P1484 种树&洛谷P3620 [APIO/CTSC 2007]数据备份 题解(堆+贪心)

    洛谷P1484 种树&洛谷P3620 [APIO/CTSC 2007]数据备份 题解(堆+贪心) 标签:题解 阅读体验:https://zybuluo.com/Junlier/note/132 ...

  9. LOJ 2743(洛谷 4365) 「九省联考 2018」秘密袭击——整体DP+插值思想

    题目:https://loj.ac/problem/2473 https://www.luogu.org/problemnew/show/P4365 参考:https://blog.csdn.net/ ...

  10. 洛谷 P7879 -「SWTR-07」How to AK NOI?(后缀自动机+线段树维护矩乘)

    洛谷题面传送门 orz 一发出题人(话说我 AC 这道题的时候,出题人好像就坐在我的右侧呢/cy/cy) 考虑一个很 naive 的 DP,\(dp_i\) 表示 \([l,i]\) 之间的字符串是否 ...

随机推荐

  1. jsp页面的跳转

    服务器端和客户端跳转 服务器端跳转不改变URL,服务器的行为 客户端跳转改变URL,客户端的行为 项目目录 index.jsp <%@ page language="java" ...

  2. SQL 条件求和

    SUMIF 就是 Excel 中的 sumif () 函数的功能. 工作中用的频率极其高, 像我就几乎天天在用的呢. 也是做个简单的笔记而已. 为啥我总是喜欢对比 Excel 呢, 因为我也渐渐发现, ...

  3. 3D Gaussian Splatting 查看工具 splatviz

    3D Gaussian Splatting 仓库自带的 SIBR Viewer 运行对显卡有要求, 需要 CUDA_ARCHITECTURE >= 7.x, 在 RTX 4060Ti 上可以运行 ...

  4. ASP.NET Core中对开放泛型(Open Generic)的依赖注入

    public interface IRepository<T> { void Add(T entity); List<T> Get(); } public class Repo ...

  5. 【语义分割专栏】:FCN原理篇

    目录 前言 语义分割 背景介绍 FCN核心剖析 全卷积(Fully Convolution) 反卷积(deconvolution) 最近邻插值法 双线性插值 反卷积 跳跃连接(Skip Connect ...

  6. arcgis创建sqlserver企业级空间数据库过程中出现的问题及解决方案

    在arcgis中创建sqlserver版本的企业空间数据库过程中,出现了多种问题,现把问题的现象.原因和解决方案记录下来,以防遗忘(年纪大了). 1 用sa账号创建空间数据库提示创建失败15456 安 ...

  7. CVE-2021-41773 && CVE-2021-42013拆解复现

    CVE-2021-41773 && CVE-2021-42013 参考了这个师傅的WP https://www.jianshu.com/p/3076d9ec68cf CVE-2021- ...

  8. 我所理解的 Go 的 CSP 并发控制机制

    你一定听说过 Go 语言所倡导的这个核心并发原则:"不要通过共享内存来通信,而要通过通信来共享内存 (Don't communicate by sharing memory; instead ...

  9. Django Web应用开发实战附录A

    Django面试题 1.Python解释器有哪些类型,有什么特点? CPython:由C语言开发,而且使用范围最广泛IPython:基于CPython的一个交互式计时器PyPy:提高执行效率,采用JI ...

  10. 「Log」2023.8.14 小记

    序幕 起晚了,七点半到校. 跟化竞选手寒暄几句之后就去开电脑. 补周末没写的博客,补落下的题单. 学杜教筛??????不会卷积????? 暂时放弃,学一下扩欧. 写了篇扩欧博客. \(\text{Li ...