树链剖分——其实不难
直接放博客——原理及一些概念
树链剖分详解(洛谷模板 P3384)
————来自 ChinHhh 大佬的博客 ——写得真的很好(我也是看这篇看懂的)
代码——
#include <bits/stdc++.h> using namespace std; #define ls p << 1 #define rs p << 1 | 1 const int maxn = 2 * 1e6 + 10; int read() //普通快读 { int f = 1, k = 0; char c = getchar(); while (c != '-' && (c < '0' || c > '9')) { c = getchar(); } if (c == '-') { f = -1; c = getchar(); } while (c >= '0' && c <= '9') { k = (k << 3) + (k << 1) + (c ^ 48); c = getchar(); } return f * k; } struct node { int u, v, nxt; } edge[maxn]; int first[maxn]; int num = 0; //这里存初始的树,用的邻接表 struct tree { int l, r, sum, siz, lazy; } t[maxn * 2]; //这里存线段树: //l,r:左右儿子。 sum:区间和。 siz:以当前节点为根的子树大小(包括了自己)。 lazy:懒标记。 int n, m, root, mod, cnt = 0, a[maxn], b[maxn]; void add(int x, int y) { num++; edge[num].u = x; edge[num].v = y; edge[num].nxt = first[x]; first[x] = num; } int deep[maxn], fa[maxn], son[maxn], tot[maxn], top[maxn], id[maxn]; int dfs1(int now, int f, int dep) { deep[now] = dep; fa[now] = f; tot[now] = 1; int maxson = -1; for (int i = first[now]; i != -1; i = edge[i].nxt) { if (edge[i].v == f) continue; tot[now] += dfs1(edge[i].v, now, dep + 1); if (tot[edge[i].v] > maxson) maxson = tot[edge[i].v], son[now] = edge[i].v; } return tot[now]; } //找重儿子 void dfs2(int now, int topf) { id[now] = ++cnt; a[cnt] = b[now]; top[now] = topf; if (!son[now]) return; dfs2(son[now], topf); for (int i = first[now]; i != -1; i = edge[i].nxt) if (!id[edge[i].v]) dfs2(edge[i].v, edge[i].v); } //处理重链 //-------------------以下为线段树 void pushup(int p) { t[p].sum = (t[ls].sum + t[rs].sum + mod) % mod; } void build(int p, int ll, int rr) { t[p].l = ll; t[p].r = rr; t[p].siz = rr - ll + 1; if (ll == rr) { t[p].sum = a[ll]; return; } int mid = (ll + rr) >> 1; build(ls, ll, mid); build(rs, mid + 1, rr); pushup(p); } void pushdown(int p) { if (!t[p].lazy) return; t[ls].sum = (t[ls].sum + t[ls].siz * t[p].lazy) % mod; t[rs].sum = (t[rs].sum + t[rs].siz * t[p].lazy) % mod; t[ls].lazy = (t[ls].lazy + t[p].lazy) % mod; t[rs].lazy = (t[rs].lazy + t[p].lazy) % mod; t[p].lazy = 0; } void intervaladd(int p, int ll, int rr, int val) //修改子树 { if (ll <= t[p].l && t[p].r <= rr) { t[p].sum += t[p].siz * val; t[p].lazy += val; return; } pushdown(p); int mid = (t[p].l + t[p].r) >> 1; if (ll <= mid) intervaladd(ls, ll, rr, val); if (rr > mid) intervaladd(rs, ll, rr, val); pushup(p); } //由于我们处理过了,所以这里的以p为根的子树的编号是连续的,故可直接用线段树的操作进行修改(相当于一个小线段树) void treeadd(int x, int y, int val) { while (top[x] != top[y]) { if (deep[top[x]] < deep[top[y]]) swap(x, y); intervaladd(1, id[top[x]], id[x], val); x = fa[top[x]]; } if (deep[x] > deep[y]) swap(x, y); intervaladd(1, id[x], id[y], val); } //想着LCA就懂了,只不过这里用的是重链 int intervalquery(int p, int ll, int rr) //求子树的和 { int ans = 0; if (ll <= t[p].l && t[p].r <= rr) return t[p].sum; pushdown(p); int mid = (t[p].l + t[p].r) >> 1; if (ll <= mid) ans = (ans + intervalquery(ls, ll, rr)) % mod; if (rr > mid) ans = (ans + intervalquery(rs, ll, rr)) % mod; return ans; } //由于我们处理过了,所以这里的以p为根的子树的编号是连续的,故可直接用线段树的操作进行修改(相当于一个小线段树) int treequery(int x, int y) //求最短路径的和,依旧和LCA差不多 { int ans = 0; while (top[x] != top[y]) { if (deep[top[x]] < deep[top[y]]) swap(x, y); ans = (ans + intervalquery(1, id[top[x]], id[x])) % mod; x = fa[top[x]]; } if (deep[x] > deep[y]) swap(x, y); ans = (ans + intervalquery(1, id[x], id[y])) % mod; return ans; } int main() { memset(first, -1, sizeof(first)); n = read(); m = read(); root = read(); mod = read(); for (int i = 1; i <= n; i++) { b[i] = read(); } for (int i = 1; i <= n - 1; i++) { int x = read(); int y = read(); add(x, y); add(y, x); } dfs1(root, 0, 1); dfs2(root, root); build(1, 1, n); while (m--) { int op = read(); int x, y, z; if (op == 1) { x = read(); y = read(); z = read(); z = z % mod; treeadd(x, y, z); } else if (op == 2) { x = read(); y = read(); printf("%d\n", treequery(x, y)); } else if (op == 3) { x = read(); z = read(); intervaladd(1, id[x], id[x] + tot[x] - 1, z % mod); } else if (op == 4) { x = read(); printf("%d\n", intervalquery(1, id[x], id[x] + tot[x] - 1)); } } return 0; }

浙公网安备 33010602011771号