題目傳送門:#8. 「模板」樹鏈剖分、
前置知識
-
重鏈:重鏈(Heavy Path)是指樹鏈剖分中的一條主要的路徑,該路徑上的節點數量較多,相鄰節點之間的距離較近。輕鏈(Light Path)是指樹鏈剖分中的其他路徑,相鄰節點之間的距離較遠。
-
LCA:最近公共祖先
分析
上樹狀陣列
首先,我們需要定義一個陣列vals來儲存每個節點的權值。然後,我們可以使用一個陣列tree來表示樹狀陣列,陣列的下標表示節點的編號。對於節點i,其在tree陣列中的下標為i,其父節點在tree陣列中的下標為i+(i&-i)
然後只需要實現以下幾個操作:
換根:將樹的根節點設定為新根節點x。我們可以將所有節點的權值都減去vals[x],然後再將vals[x]加到所有節點的權值中即可。
修改路徑權值:給定兩個節點u和v,將節點u到節點v的路徑上的所有節點權值增加一個給定的值x。我們可以先將節點u的權值增加x,然後再將節點v的權值減去x。最後將tree陣列中節點u到節點v的路徑上的節點的值都增加x。
修改子樹權值:給定一個節點x,將以節點x為根的子樹內的所有節點權值增加一個給定的值y。我們可以將節點x的權值增加y,然後將tree陣列中節點x及其所有子節點的值都增加y。
詢問路徑:給定兩個節點u和v,詢問節點u到節點v的路徑上所有節點權值的和。我們可以先計算節點v到根節點的權值和,再計算節點u的權值,最後將兩者相減即可。
詢問子樹:給定一個節點x,詢問以節點x為根的子樹內所有節點權值的和。我們可以直接返回tree陣列中節點x及其所有子節點的值的和。
✿✿ヽ(°▽°)ノ✿
等等,還要上程式碼
Code
10分
#include <cstdio>
#define reg register
int read() {
reg int s = 0, f = 1;
reg char ch;
for (; (ch = getchar()) < '0' || ch > '9'; ch == '-' ? f = -f : 0)
;
for (; ch >= '0' && ch <= '9'; s = (s << 3) + (s << 1) + ch - 48, ch = getchar())
;
return s * f;
}
const int N = 1e5 + 50;
int next[N], to[N], first[N], tag[N << 2], tree[N << 2], val[N], cnt = 0, tot = 0, n, m;
int dfn[N], son[N], size[N], efn[N], dep[N], top[N], fa[N], root, g[N];
inline void push_up(int now) { tree[now] = tree[now << 1] + tree[now << 1 | 1]; }
inline void add(int now, int l, int r, int v) {
tree[now] += (r - l + 1) * v;
tag[now] += v;
}
void push_down(int now, int l, int r) {
if (tag[now]) {
int mid = (l + r) >> 1;
add(now << 1, l, mid, tag[now]);
add(now << 1 | 1, mid + 1, r, tag[now]);
tag[now] = 0;
}
}
void _add(int u, int v) { next[++cnt] = first[u], first[u] = cnt, to[cnt] = v; }
void build(int now, int l, int r) {
if (l == r) {
tree[now] = val[g[l]];
return;
}
int mid = (l + r) >> 1;
build(now << 1, l, mid);
build(now << 1 | 1, mid + 1, r);
push_up(now);
}
void mobify(int now, int l, int r, int L, int R, int v) {
if (L <= l && r <= R) {
add(now, l, r, v);
return;
}
int mid = (l + r) >> 1;
push_down(now, l, r);
if (L <= mid)
mobify(now << 1, l, mid, L, R, v);
if (R > mid)
mobify(now << 1 | 1, mid + 1, r, L, R, v);
push_up(now);
}
int query(int now, int l, int r, int L, int R) {
if (L <= l && r <= R)
return tree[now];
int mid = (l + r) >> 1, ans = 0;
push_down(now, l, r);
if (L <= mid)
ans += query(now << 1, l, mid, L, R);
if (R > mid)
ans += query(now << 1 | 1, mid + 1, r, L, R);
return ans;
}
void dfs0(int now) {
son[now] = 0;
size[now] = 1;
for (reg int i = first[now]; i; i = next[i])
if (!dep[to[i]]) {
fa[to[i]] = now;
dep[to[i]] = dep[now] + 1;
dfs0(to[i]);
size[now] += size[to[i]];
if (!son[now] || size[to[i]] > size[son[now]])
son[now] = to[i];
}
}
void dfs1(int now, int t) {
dfn[now] = ++tot;
top[now] = t;
g[tot] = now;
if (son[now])
dfs1(son[now], t);
for (reg int i = first[now]; i; i = next[i])
if (!dfn[to[i]])
dfs1(to[i], to[i]);
efn[now] = tot;
}
int lca(int u, int v) {
while (top[u] != top[v]) (dep[top[u]] > dep[top[v]]) ? u = fa[top[u]] : v = fa[top[v]];
return (dep[u] < dep[v]) ? u : v;
}
void addpath(int u, int v, int k) {
while (top[u] != top[v])
(dep[top[u]] > dep[top[v]]) ? (mobify(1, 1, n, dfn[top[u]], dfn[u], k), u = fa[top[u]])
: (mobify(1, 1, n, dfn[top[v]], dfn[v], k), v = fa[top[v]]);
(dep[u] < dep[v]) ? mobify(1, 1, n, dfn[u], dfn[v], k) : mobify(1, 1, n, dfn[v], dfn[u], k);
}
void addtree(int u, int k) {
if (u == root) {
mobify(1, 1, n, 1, n, k);
return;
}
int g = lca(u, root);
if (g != u)
mobify(1, 1, n, dfn[u], efn[u], k); // puts("wtf");
else {
// puts("wtm");
if (dfn[root] > 1)
mobify(1, 1, n, 1, dfn[root] - 1, k);
if (efn[root] < n)
mobify(1, 1, n, efn[root] + 1, n, k);
// mobify(1,1,n,1,dfn[u]-1,k);
// mobify(1,1,n,efn[u]+1,n,k);
}
}
void querypath(int u, int v) {
int ans = 0;
while (top[u] != top[v])
if (dep[top[u]] > dep[top[v]])
ans += query(1, 1, n, dfn[top[u]], dfn[u]), u = fa[top[u]];
else
ans += query(1, 1, n, dfn[top[v]], dfn[v]), v = fa[top[v]];
// printf("%d %d %d\n",u,v,ans);
ans += (dep[u] < dep[v]) ? query(1, 1, n, dfn[u], dfn[v]) : query(1, 1, n, dfn[v], dfn[u]);
printf("%d\n", ans);
}
void querytree(int u) {
if (u == root) {
printf("%d\n", tree[1]);
return;
}
int g = lca(u, root);
if (g != u)
printf("%d\n", query(1, 1, n, dfn[u], efn[u]));
else {
int ans = 0;
if (dfn[root] > 1)
ans += query(1, 1, n, 1, dfn[root] - 1);
if (efn[root] < n)
ans += query(1, 1, n, efn[root] + 1, n);
printf("%d\n", ans);
}
}
int main() {
n = read();
root = 1;
for (reg int i = 1; i <= n; i++) val[i] = read();
for (reg int u = 2, v; u <= n; u++) v = read(), _add(v, u);
m = read();
dep[1] = 1;
dfs0(1);
dfs1(1, 1);
build(1, 1, n);
for (reg int i = 1, opt, u, v, k; i <= m; i++) {
opt = read();
if (opt == 1)
root = read();
if (opt == 2)
u = read(), v = read(), k = read(), addpath(u, v, k);
if (opt == 3)
u = read(), k = read(), addtree(u, k);
if (opt == 4)
u = read(), v = read(), querypath(u, v);
if (opt == 5)
u = read(), querytree(u);
}
return 0;
}
100分程式碼
#include <bits/stdc++.h>
#define INF 0x7fffffff
#define ll long long
using namespace std;
ll read() {
ll k = 1, x = 0;
char ch = getchar();
while (ch < '0' || ch > '9') {
if (ch == '-')
k = -1;
ch = getchar();
}
while (ch >= '0' && ch <= '9') x = (x << 3) + (x << 1) + ch - 48, ch = getchar();
return k * x;
}
const int N = 3e5 + 10;
struct EDGE {
ll to, pre;
} edge[N];
ll a[N], size[N], fa[N], dep[N], id[N], w[N], son[N], top[N], head[N];
ll cnt, n, m, r;
//========== 關於換根的操作 =========
inline ll find(ll u, ll v) { //找從u(題目查詢點的)到root第一個兒子
while (top[u] != top[v]) {
if (dep[top[u]] > dep[top[v]])
swap(u, v); //保證u的深度更淺
if (fa[top[v]] == u)
return top[v]; //若鏈頭的父親恰好為u返回鏈頭
v = fa[top[v]]; //不斷向上跳
}
if (dep[u] > dep[v])
swap(u, v);
return son[u];
}
inline ll lca(ll u, ll v) {
while (top[u] != top[v]) {
if (dep[top[u]] > dep[top[v]])
swap(u, v);
v = fa[top[v]];
}
return dep[u] >= dep[v] ? v : u;
}
//==================================
//================== 以下為線段樹模板 ====================
#define up(x) t[x].sum = t[x << 1].sum + t[x << 1 | 1].sum
struct node {
ll l, r;
ll sum, lazy;
} t[N << 2];
ll len(ll p) { return t[p].r - t[p].l + 1; }
void brush(ll p, ll k) {
t[p].lazy += k;
t[p].sum += len(p) * k;
}
void push_down(ll p) {
brush(p << 1, t[p].lazy);
brush(p << 1 | 1, t[p].lazy);
t[p].lazy = 0;
}
void build(ll p, ll l, ll r) {
t[p].l = l;
t[p].r = r;
if (l == r) {
t[p].sum = w[l];
return;
}
ll mid = l + r >> 1;
build(p << 1, l, mid);
build(p << 1 | 1, mid + 1, r);
up(p);
}
void update(ll p, ll l, ll r, ll k) {
if (t[p].l >= l && t[p].r <= r) {
brush(p, k);
return;
}
push_down(p);
ll mid = t[p].l + t[p].r >> 1;
if (mid >= l)
update(p << 1, l, r, k);
if (r >= mid + 1)
update(p << 1 | 1, l, r, k);
up(p);
}
ll getans(ll p, ll l, ll r) {
if (t[p].l >= l && t[p].r <= r)
return t[p].sum;
push_down(p);
ll ans = 0;
ll mid = t[p].l + t[p].r >> 1;
if (l <= mid)
ans += getans(p << 1, l, r);
if (r >= mid + 1)
ans += getans(p << 1 | 1, l, r);
return ans;
}
//================== 以上為線段樹模板 ====================
//================== 以下為樹剖操作模板 ====================
inline void updatetree(ll s, ll t, ll c) { // s到t的簡單路徑加上c
while (top[s] != top[t]) { //倍增思想,深度大的往上跳
if (dep[top[s]] > dep[top[t]])
swap(s, t); //預設t深度大
update(1, id[top[t]], id[t], c); //先維護這一段鏈
t = fa[top[t]]; //跳到這個鏈頭的父節點,維護下一個鏈
}
if (dep[s] > dep[t])
swap(s, t); //預設t深度大
update(1, id[s], id[t], c); //維護s與現在t(同深度)的鏈
}
inline ll getanstree(ll s, ll t) { //查詢s到t的簡單路徑權值和
ll ans = 0;
while (top[s] != top[t]) { //倍增思想,深度大的往上跳
if (dep[top[s]] > dep[top[t]])
swap(s, t); //預設t深度大
ans += getans(1, id[top[t]], id[t]); //先查詢這一段鏈
t = fa[top[t]]; //跳到這個鏈頭的父節點,查詢下一個鏈
}
if (dep[s] > dep[t])
swap(s, t); //預設t深度大
ans += getans(1, id[s], id[t]); //查詢s與現在t(同深度)的鏈
return ans;
}
inline void updateson(ll x, ll t) { //以x為根的子樹加上t
if (x == r)
update(1, 1, n, t); //當 x=root時,x就是此時整棵樹的根,那麼就是全域性修改。
else {
ll LCA = lca(x, r);
if (LCA != x) //若root不在x的子樹中,正常修改
update(1, id[x], id[x] + size[x] - 1, t);
else { //若在子樹內,修改它到根的第一個兒子子樹的補集
ll u = find(x, r);
update(1, 1, n, t);
update(1, id[u], id[u] + size[u] - 1, -t);
}
}
}
inline ll getansson(ll x) { //以x為根的子樹加上t
if (x == r)
return getans(1, 1, n); //當 x=root時,x就是此時整棵樹的根,那麼就是全域性查詢。
else {
ll LCA = lca(x, r);
if (LCA != x) //若root不在x的子樹中,正常查詢
return getans(1, id[x], id[x] + size[x] - 1);
else { //若在子樹內,查詢它到根的第一個兒子子樹的補集
ll u = find(x, r);
return getans(1, 1, n) - getans(1, id[u], id[u] + size[u] - 1);
}
}
}
//================== 以上為樹剖操作模板 ====================
//================== 以下為dfs ====================
inline void dfs1(ll x, ll fath, ll deep) { // x當前節點,f父親,deep深度
dep[x] = deep; //標記每個點的深度
fa[x] = fath; //標記每個點的父親
size[x] = 1; //標記每個非葉子節點的子樹大小
ll maxson = -1; //記錄重兒子的兒子數
for (ll i = head[x]; i; i = edge[i].pre) {
ll y = edge[i].to;
if (y == fath)
continue;
dfs1(y, x, deep + 1);
size[x] += size[y]; //把它的兒子數加到它身上
if (size[y] > maxson) { //標記每個非葉子節點的重兒子編號
son[x] = y;
maxson = size[y];
}
}
}
ll tim;
inline void dfs2(ll x, ll fst) { // x當前節點,fst當前鏈的頂端
id[x] = ++tim; //標記每個點dfs序
w[tim] = a[x]; //賦每個點的初始值
top[x] = fst; //標記這個點所在鏈的頂端
if (!son[x])
return; //如果沒有兒子則返回
dfs2(son[x], fst); //按先處理重兒子
for (ll i = head[x]; i; i = edge[i].pre) {
ll y = edge[i].to;
if (y == fa[x] || y == son[x])
continue;
dfs2(y, y); //輕兒子都有一條從它自己開始的鏈
}
}
//================== 以上為dfs ====================
inline void add(ll u, ll v) { //鏈式前向星
edge[++cnt].pre = head[u];
edge[cnt].to = v;
head[u] = cnt;
}
int main() {
n = read();
r = 1;
for (ll i = 1; i <= n; i++) a[i] = read();
for (ll i = 1; i < n; i++) {
ll a;
a = read();
add(a, i + 1), add(i + 1, a);
}
dfs1(r, 0, 1);
dfs2(r, r);
build(1, 1, n);
m = read();
for (ll i = 1; i <= m; i++) {
ll opt = read(), x = read();
if (opt == 1)
r = x;
if (opt == 2) {
ll y = read(), z = read();
updatetree(x, y, z);
}
if (opt == 4) {
ll y = read();
cout << getanstree(x, y) << endl;
}
if (opt == 3) {
ll z = read();
updateson(x, z);
}
if (opt == 5)
cout << getansson(x) << endl;
}
return 0;
}