洛谷P3384 【模板】轻重链剖分
题目描述
如题,已知一棵包含 NNN 个结点的树(连通且无环),每个节点上包含一个数值,需要支持以下操作:
操作 111: 格式: 1 x y z1\ x\ y\ z1 x y z 表示将树从 xxx 到 yyy 结点最短路径上所有节点的值都加上 zzz。
操作 222: 格式: 2 x y2\ x\ y2 x y 表示求树从 xxx 到 yyy 结点最短路径上所有节点的值之和。
操作 333: 格式: 3 x z3\ x\ z3 x z 表示将以 xxx 为根节点的子树内所有节点值都加上 zzz。
操作 444: 格式: 4 x4\ x4 x 表示求以 xxx 为根节点的子树内所有节点值之和
输入格式
第一行包含 444 个正整数 N,M,R,PN,M,R,PN,M,R,P,分别表示树的结点个数、操作个数、根节点序号和取模数(即所有的输出结果均对此取模)。
接下来一行包含 NNN 个非负整数,分别依次表示各个节点上初始的数值。
接下来 N−1N-1N−1 行每行包含两个整数 x,yx,yx,y,表示点 xxx 和点 yyy 之间连有一条边(保证无环且连通)。
接下来 MMM 行每行包含若干个正整数,每行表示一个操作,格式如下:
操作 111: 1 x y z1\ x\ y\ z1 x y z;
操作 222: 2 x y2\ x\ y2 x y;
操作 333: 3 x z3\ x\ z3 x z;
操作 444: 4 x4\ x4 x。
输出格式
输出包含若干行,分别依次表示每个操作 222 或操作 444 所得的结果(对 PPP 取模)。
输入输出样例
输入 #1
5 5 2 24 7 3 7 8 0 1 2 1 5 3 1 4 1 3 4 2 3 2 2 4 5 1 5 1 3 2 1 3
输出 #1
2 21
人家讲的比我好,就不写题解了T^T https://www.luogu.com.cn/problem/solution/P3384
需要注意的点都在注释
#include <bits/stdc++.h> #define N 100005 using namespace std; int size[N], son[N], top[N], dep[N], fa[N], dfn[N], wt[N], head[N], ver[N * 2], Next[N * 2], w[N], n, m, r, mod, tot, cnt = 0; void add(int x, int y) { ver[++tot] = y, Next[tot] = head[x], head[x] = tot; } /*----------下为线段树----------*/ struct SegmentTree { int l; int r; int sum; int add; } t[4 * N]; void build(int p, int l, int r) { t[p].l = l; t[p].r = r; if(l == r) { t[p].sum = wt[l];//无效模可能会超时? if(t[p].sum >= mod) t[p].sum %= mod; return; } int mid = (l + r) >> 1; build(2 * p, l, mid); build(2 * p + 1, mid + 1, r); t[p].sum = (t[2 * p].sum + t[2 * p + 1].sum) % mod; } void spread(int p) { if(t[p].add) { t[2 * p].sum += t[p].add * (t[2 * p].r - t[2 * p].l + 1) % mod; t[2 * p + 1].sum += t[p].add * (t[2 * p + 1].r - t[2 * p + 1].l + 1) % mod; t[2 * p].add += t[p].add; t[2 * p + 1].add += t[p].add; t[p].add = 0; } } int ask(int p, int l, int r) { if(t[p].l >= l && t[p].r <= r) { return t[p].sum % mod; } spread(p); int mid = (t[p].l + t[p].r) >> 1; int val = 0; if(l <= mid) val = (val + ask(2 * p, l, r)) % mod; if(r > mid) val = (val + ask(2 * p + 1, l, r)) % mod; return val; } void change(int p, int l, int r, int d) { if(t[p].l >= l && t[p].r <= r) { t[p].sum = (t[p].sum + d * (t[p].r - t[p].l + 1) % mod) % mod; t[p].add += d; return; } spread(p); int mid = (t[p].l + t[p].r) >> 1; if(l <= mid) change(2 * p, l, r, d); if(r > mid) change(2 * p + 1, l, r, d); t[p].sum = (t[2 * p].sum + t[2 * p + 1].sum) % mod; } /*----------下为树剖预处理----------*/ void dfs1(int x, int f, int deep)//x为当前节点, f为父亲, deep为深度 { dep[x] = deep; fa[x] = f; size[x] = 1;//记录每个非叶子节点的子树大小(包括自己) for(int i = head[x]; i; i = Next[i]) { int y = ver[i]; if(y == f) continue; dfs1(y, x, deep + 1); size[x] += size[y]; if(size[y] > size[son[x]]) son[x] = y;//更新重儿子 } } void dfs2(int x, int topf)//topf是当前链最顶端的节点 { dfn[x] = ++cnt; wt[cnt] = w[x]; top[x] = topf; if(!son[x]) return; dfs2(son[x], topf);//保证重链连续性, 先搜索重儿子 for(int i = head[x]; i; i = Next[i]) { int y = ver[i]; if(y == fa[x] || y == son[x]) continue; dfs2(y, y);//对于每个轻儿子有一条从他自己开始的链 } } /*----------下为s树剖查询等----------*/ inline int qRange(int x, int y) { int ans = 0; while(top[x] != top[y])//当两个点还没跳到同一条链上 { if(dep[top[x]] < dep[top[y]]) swap(x, y); ans = (ans + ask(1, dfn[top[x]], dfn[x])) % mod; x = fa[top[x]]; } //直到两个点处于同一条链上 if(dep[x] > dep[y]) swap(x, y); return (ans + ask(1, dfn[x], dfn[y])) % mod; } inline int qSon(int x) { return ask(1, dfn[x], dfn[x] + size[x] - 1);//子树也是连号的 } inline void updateRange(int x, int y, int k) { k %= mod; while(top[x] != top[y])//当两个点还没跳到同一条链上 { if(dep[top[x]] < dep[top[y]]) swap(x, y); change(1, dfn[top[x]], dfn[x], k); x = fa[top[x]];; } if(dep[x] > dep[y]) swap(x, y); change(1, dfn[x], dfn[y], k); } inline void updateSon(int x, int k) { change(1, dfn[x], dfn[x] + size[x] - 1, k); } int main() { cin >> n >> m >> r >> mod; int i; for(i = 1; i <= n; i++) scanf("%d", &w[i]); for(i = 1; i <= n - 1; i++) { int x, y; scanf("%d%d", &x, &y); add(x, y); add(y, x); } dfs1(r, 0, 1);//别忘先dfs后建树! dfs2(r, r); build(1, 1, n); for(i = 1; i <= m; i++) { int op, x, y, z; scanf("%d", &op); if(op == 1) { scanf("%d%d%d", &x, &y, &z); updateRange(x, y, z); } else if(op == 2) { scanf("%d%d", &x, &y); cout << qRange(x, y) << endl; } else if(op == 3) { scanf("%d%d", &x, &z); updateSon(x, z); } else { scanf("%d", &x); cout << qSon(x) << endl; } } return 0; }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!