1. 程式人生 > >「洛谷1600」「NOIP2016提高組」天天愛跑步【樹上差分】

「洛谷1600」「NOIP2016提高組」天天愛跑步【樹上差分】

題目 給定 nod new nlogn 否則 top class 分享圖片

閑話

為了理清這道題目的思路,我是邊寫博客邊做題的,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提高組」天天愛跑步【樹上差分】