「洛谷1600」「NOIP2016提高組」天天愛跑步【樹上差分】
閑話
為了理清這道題目的思路,我是邊寫博客邊做題的,qwq。
題目鏈接
洛谷
題解
首先對變量進行聲明
dep[i] 表示i號節點的深度,是到根節點的深度
w[i] 表示i號觀測點觀測的時間
dfn[i] 表示i號點的dfn序
sz[i] 表示i號點的子樹大小
以上圖為例,藍色點表示一個玩家的起點和終點。
不妨先假設左邊的點是起點,右邊的點是終點,分別用\(s\)和\(t\)來表示
那麽可能對答案產生貢獻的點一定是在這個從\(s\)到\(t\)上的觀測點。
記觀測點為\(g\)。
對這個問題進行分類討論
Case 1:觀測點在起點到\(LCA\)的路徑上
我們把這個情況記為觀測點在\(A\)
如果這個觀測點能夠觀測到這個起點,那麽一定滿足以下的式子
\[dep[s]-dep[g]=w[g]\]
可以從圖中觀察到,\(dep[s]-dep[g]\)的值就是\(s\)到觀測點\(g\)的時間長度。
為什麽不需要\(±1\),是因為時間一開始是從\(0\)開始計數的。
換句話說,就是實際的通過時間就是通過邊的數量或者是路徑上經過點的數量\(-1\)。
對於現有式子進行變形。
\[dep[s]=dep[g]+w[g]\]
可以發現等式的右邊是題目給定的定值。
那麽問題就變成了對於每一個在\(A\)類路徑上的觀測點,起點的深度等於\(dep[g]+w[g]\)的個數。
暴力求解這個問題的復雜度差不多是\(n\times (dep[g]+w[g]-dep[s])\)
我們可以把這個問題變一下,變成在以\(g\)為根的子樹內,有多少個起點滿足以上的性質。
轉換成子樹的問題就可以用樹鏈剖分維護了。
先扯一個常識:在樹上,一棵以\(u\)為根節點的子樹的區間是\([dfn[u],sz[u]+dfn[u]-1]\)
問題轉化成了:在區間\([dfn[u],sz[u]+dfn[u]-1]\)中有多少個深度等於\(dep[g]+w[g]\)的起點的個數。
可以對每一個深度建立一棵線段樹。(動態開點,否則會MT飛掉)
那麽問題就變成了在深度為\(dep[g]+w[g]\)的線段樹中在區間\([dfn[u],sz[u]+dfn[u]-1]\)
如果暴力修改區間並且查詢,修改的復雜度是\(O(nlog^2n)\),來一條鏈就爆炸了。
考慮樹上差分,其實這個東西我也想了很久,也不知道為什麽可以差分,但是其實挺簡單的。
為什麽可以差分?
差分是什麽?
差分只的前一個答案和後一個答案之間的差值。
在樹上也就變成了祖先和兒子之間的關系。
先不要管線段樹這個東西,會妨礙我們思考。
因為我們都知道,如果用差分計算一棵樹上的答案,那麽就是這顆樹裏面所有差分值全部\(+\)起來。
在這裏因為路徑\(s->lca\)的答案只有\(s\)這個起點有貢獻。
模擬一下,如果是\(s\)的子樹,很明顯這個答案不會產生貢獻。
如果是\(s->lca\)的鏈上,這個答案會對\(g\)貢獻\(+1\)。
如果是lca以上的祖先,那麽就在\(lca\)上打一個\(-1\)的標記。
抽象的概念就是:在這個深度上只有\(s->lca\)這一段區間的答案可以\(+1\)。
那麽套一個線段樹就可以了。
Case 2:觀測點在\(LCA\)到終點的路徑上
下面一半就簡單了,圖我就不畫了。
得到產生貢獻的式子
\[dep[s]+dep[g]-2\times dep[lca]=w[g]\]
前一半的式子其實就是求\(s->g\)的最短距離。
推導得到
\[dep[s]-2dep[lca]=w[g]-dep[g]\]
等式右邊又是一個定值。
仿照上面的套路:對於深度建立線段樹,打樹上差分標記。
總的時間復雜度是:\(O(nlogn)\)
tips
- Case 2中的差值可能<0,需要整體右移,查詢的時候也要整體右移,大小自己控制。
- 不要兩次都把\(-1\)的標記都打在\(lca\)上,這樣會減掉\(lca\)的答案,有一個標記打在\(lca\)的父親上。
- 做完一遍後要請空數組。
Code
#include <bits/stdc++.h>
using namespace std;
namespace IOstream {
#define gc getchar
template <typename T> inline void read(T &x) {
x = 0; T fl = 1; char c = 0;
for (; c < '0' || c > '9'; c = gc()) if (c == '-') fl = -1;
for (; c >= '0' && c <= '9'; c = gc()) x = (x << 1) + (x << 3) + (c ^ 48);
x *= fl;
}
template <typename T> inline void write(T x) {
if (x < 0) putchar('-'), x *= -1;
if (x > 9) write(x / 10);
putchar(x % 10 + '0');
}
template <typename T> inline void writeln(T x) { write(x); puts(""); }
template <typename T> inline void writesp(T x) { write(x); putchar(' '); }
#undef gc
} using namespace IOstream;
const int N = 3e6 + 5;
struct edge {
int to, nt;
} E[N << 1];
struct pl {
int s, t, lca;
} a[N];
int H[N];
int sz[N], fa[N], dep[N], son[N], top[N], dfn[N], rt[N];
int ecnt, n, m, __dfn = 0;
int w[N], ans[N];
void add_edge(int u, int v) {
E[++ ecnt] = (edge) {v, H[u]};
H[u] = ecnt;
}
void dfs1(int u, int ft = 0) {
fa[u] = ft; sz[u] = 1; dep[u] = dep[ft] + 1;
int maxson = -1;
for (int e = H[u]; e; e = E[e].nt) {
int v = E[e].to;
if (v == fa[u]) continue;
dfs1(v, u);
sz[u] += sz[v];
if (sz[v] > maxson) maxson = sz[v], son[u] = v;
}
}
void dfs2(int u, int tp = 1) {
top[u] = tp;
dfn[u] = ++ __dfn;
if (!son[u]) return;
dfs2(son[u], top[u]);
for (int e = H[u]; e; e = E[e].nt) {
int v = E[e].to;
if (v == fa[u] || v == son[u]) continue;
dfs2(v, v);
}
}
int LCA(int u, int v) {
while (top[u] != top[v]) {
if (dep[top[u]] < dep[top[v]]) swap(u, v);
u = fa[top[u]];
}
return dep[u] < dep[v] ? u : v;
}
namespace seg {
int tot;
struct node {
int lc, rc, s;
} tr[N * 10];
void clear() { tot = 0; memset(tr, 0, sizeof(tr)); }
void upd(int &nod, int l, int r, int k, int val) {
if (!k) return;
if (!nod) nod = ++ tot;
tr[nod].s += val;
if (l == r) return;
int mid = (l + r) >> 1;
if (k <= mid) upd(tr[nod].lc, l, mid, k, val);
else upd(tr[nod].rc, mid + 1, r, k, val);
}
int query(int nod, int l, int r, int ql, int qr) {
if (!nod) return 0;
if (ql <= l && r <= qr) return tr[nod].s;
int mid = (l + r) >> 1, res = 0;
if (ql <= mid) res += query(tr[nod].lc, l, mid, ql, qr);
if (qr > mid) res += query(tr[nod].rc, mid + 1, r, ql, qr);
return res;
}
}
signed main() {
read(n); read(m);
for (int i = 1, u, v; i < n; i ++) {
read(u); read(v);
add_edge(u, v); add_edge(v, u);
}
dep[0] = 0; dfs1(1);
dfs2(1);
for (int i = 1; i <= n; i ++) read(w[i]);
for (int i = 1; i <= m; i ++) {
read(a[i].s); read(a[i].t);
a[i].lca = LCA(a[i].s, a[i].t);
}
seg::clear(); memset(rt, 0, sizeof(rt));
for (int i = 1; i <= m; i ++) {
int root = dep[a[i].s];
seg::upd(rt[root], 1, n, dfn[a[i].s], 1);
seg::upd(rt[root], 1, n, dfn[fa[a[i].lca]], -1);
}
for (int i = 1; i <= n; i ++)
ans[i] += seg::query(rt[dep[i] + w[i]], 1, n, dfn[i], sz[i] + dfn[i] - 1);
seg::clear(); memset(rt, 0, sizeof(rt));
for (int i = 1; i <= m; i ++) {
int root = dep[a[i].s] - dep[a[i].lca] * 2 + 2 * n;
seg::upd(rt[root], 1, n, dfn[a[i].t], 1);
seg::upd(rt[root], 1, n, dfn[a[i].lca], -1);
}
for (int i = 1; i <= n; i ++)
ans[i] += seg::query(rt[w[i] - dep[i] + 2 * n], 1, n, dfn[i], sz[i] + dfn[i] - 1);
for (int i = 1; i <= n; i ++)
writesp(ans[i]);
return 0;
}
「洛谷1600」「NOIP2016提高組」天天愛跑步【樹上差分】