P1600 [NOIP2016 提高組] 天天愛跑步 題解
阿新 • • 發佈:2022-05-11
線段樹合併 + 樹上差分的好題。
考慮對每個點開一個值域線段樹維護值而不是深度,值域 \([-n,n]\),預處理 LCA 和 每個點的深度。
將 \(s \to t\) 的路徑拆成兩段,一段是 \(s \to lca\):
注意到此時 \(s \to lca\) 的路徑上所有點 \(x\),設其被走到的時間為 \(t_x\),則所有 \(x\) 滿足 \(dep_x + t_x\) 為定值。
據此我們在 \(s \to lca\) 這一段鏈上每個點的線段樹 \(dep_s\) 的對應位置加 1,樹上差分即可。
另一端是 \(lca \to t\):
注意到此時 \(lca \to t\)
據此我們在 \(lca \to t\) 這一段鏈上每個點線段樹 \(dep_t - (dep_t + dep_s - 2 * dep_{lca})\) 的對應位置加 1,樹上差分即可。
然而這並不是簡單樹上差分就好了,因為 lca 兩個條件同時滿足,而這麼做 lca 這個點查詢時會被算 2 次,所以我們在 s,t 兩個點上打完標記後 lca 這個點對應位置取一個減一,然後其父親對應位置取另一個減一即可。
實際上上述操作就是將 s 到 lca 的下端作為第一種情況,\(lca \to t\)
樹上差分之後做線段樹合併即可,查詢時詢問 \(dep_x+w_x\) 和 \(dep_x-w_x\) 兩個位置的值。
可以寫兩棵線段樹分別維護,但是也可以只寫一棵因為上述推斷條件時是充要條件,寫一棵時注意 \(w_x=0\) 只查詢一次。
線段樹維護的值域為 \([-n,n]\),當然也可以整體平移成 \([0,2n]\)。
GitHub:CodeBase-of-Plozia
Code:
/* ========= Plozia ========= Author:Plozia Problem:P1600 [NOIP2016 提高組] 天天愛跑步 Date:2022/5/10 ========= Plozia ========= */ #include <bits/stdc++.h> typedef long long LL; const int MAXN = 3e5 + 5; int n, m, Head[MAXN], cntEdge, dep[MAXN], fa[MAXN][21], w[MAXN], Root[MAXN], ans[MAXN], cntSgT; struct node { int To, Next; } Edge[MAXN << 1]; struct SgT { int val, ls, rs; } tree[MAXN * 40]; #define val(p) tree[p].val #define ls(p) tree[p].ls #define rs(p) tree[p].rs int Read() { int sum = 0, fh = 1; char ch = getchar(); for (; ch < '0' || ch > '9'; ch = getchar()) fh -= (ch == '-') << 1; for (; ch >= '0' && ch <= '9'; ch = getchar()) sum = sum * 10 + (ch ^ 48); return sum * fh; } int Max(int fir, int sec) { return (fir > sec) ? fir : sec; } int Min(int fir, int sec) { return (fir < sec) ? fir : sec; } void addEdge(int x, int y) { Edge[++cntEdge] = (node){y, Head[x]}; Head[x] = cntEdge; } void dfs1(int now, int father) { dep[now] = dep[father] + 1; fa[now][0] = father; for (int i = Head[now]; i; i = Edge[i].Next) { int u = Edge[i].To; if (u == father) continue ; dfs1(u, now); } } void init() { for (int j = 1; j <= 20; ++j) for (int i = 1; i <= n; ++i) fa[i][j] = fa[fa[i][j - 1]][j - 1]; } int LCA(int x, int y) { if (dep[x] < dep[y]) std::swap(x, y); for (int i = 20; i >= 0; --i) if (dep[fa[x][i]] >= dep[y]) x = fa[x][i]; if (x == y) return x; for (int i = 20; i >= 0; --i) if (fa[x][i] != fa[y][i]) x = fa[x][i], y = fa[y][i]; return fa[x][0]; } void Insert(int &p, int x, int k, int lp, int rp) { if (!p) p = ++cntSgT; if (x < lp || x > rp) return ; if (lp == rp) { val(p) += k; return ; } int mid = (lp + rp) >> 1; if (x <= mid) Insert(ls(p), x, k, lp, mid); else Insert(rs(p), x, k, mid + 1, rp); } void Merge(int &p1, int p2, int lp, int rp) { if (!p1 || !p2) { p1 = p1 + p2; return ; } if (lp == rp) { val(p1) += val(p2); return ; } int mid = (lp + rp) >> 1; Merge(ls(p1), ls(p2), lp, mid); Merge(rs(p1), rs(p2), mid + 1, rp); } int Ask(int p, int x, int lp, int rp) { if (!p || x < lp || x > rp) return 0; if (lp == rp) return val(p); int mid = (lp + rp) >> 1; if (x <= mid) return Ask(ls(p), x, lp, mid); else return Ask(rs(p), x, mid + 1, rp); } void dfs2(int now, int father) { for (int i = Head[now]; i; i = Edge[i].Next) { int u = Edge[i].To; if (u == father) continue ; dfs2(u, now); Merge(Root[now], Root[u], -n, n); } ans[now] = Ask(Root[now], dep[now] + w[now], -n, n); if (w[now] != 0) ans[now] += Ask(Root[now], dep[now] - w[now], -n, n); } int main() { n = Read(), m = Read(); for (int i = 1; i < n; ++i) { int x = Read(), y = Read(); addEdge(x, y); addEdge(y, x); } dfs1(1, 0); init(); for (int i = 1; i <= n; ++i) w[i] = Read(); for (int i = 1; i <= m; ++i) { int x = Read(), y = Read(), lca = LCA(x, y); Insert(Root[x], dep[x], 1, -n, n); Insert(Root[y], dep[y] - (dep[x] + dep[y] - 2 * dep[lca]), 1, -n, n); Insert(Root[lca], dep[x], -1, -n, n); Insert(Root[fa[lca][0]], dep[y] - (dep[x] + dep[y] - 2 * dep[lca]), -1, -n, n); } dfs2(1, 1); for (int i = 1; i <= n; ++i) printf("%d%c", ans[i], " \n"[i == n]); return 0; }