路徑上若干條樹的包含

XuYueming發表於2024-11-09

題意

別人今天期中考,而你依然在機房裡為今年的 NOIP 努力刷題。

一道兩道三四道,紫題黑題不會題。
暴力列舉TLE,陣列開小爆零寄……

已過立冬,窗外寒風瑟瑟,但是你看到有一棵樹屹立不倒,你也想成為像大樹一般挺拔的人。

一、二、三……你仔細數著,發現樹上有 \(n\) 個結點。

忽然你手裡多出了一個東西,鼓鼓的,原來是一個大小為 \(m\) 的集合。集合裡有什麼呢?你思索著,開啟了集合。原來集合裡鼓鼓囊囊裝著 \(m\) 條樹上的路徑。這真是一棵神奇的樹呢。你瞅了瞅集合外包裝,說明上表明這是由 yzh 公司研發的可重集合,真是個新鮮玩意呢。

“你好啊,OIer!”那棵樹朝你說道,“我們來玩個遊戲吧。”你當然答應下來。

“遊戲有 \(q\) 輪。”竟然是 \(q\) 輪,而不是 \(q^{\sqrt{e}!}\) 輪或者 \(e^{iq\pi}\) 輪,真是有趣呢,你想道。

“每次我可以向你手上的集合裡塞入新的一條路徑……”這就是它的魔力嗎?

“或者我給你一條路徑,你要告訴我它完全包含了集合中多少條路徑,怎麼樣?”你爽快答應下來了,真是一個好玩的遊戲呢。思索片刻,你發現這竟然和你最近做的題目有些相像……


形式化題意:

一棵 \(n\) 個結點的樹,你需要維護路徑可重集 \(S\),初始有 \(m\) 條路徑。有 \(q\) 次操作:

  1. 查詢路徑 \((u, v)\) 完全包含 \(S\) 中多少條路徑;
  2. \(S\) 中插入一條路徑;
  3. \(S\) 中刪除一條路徑。

\(n, m, q \leq 10^5\),保證路徑不是一個點,沒有第三種操作。事實上,正解可以處理每條路徑具有不同權值,刪除即為貢獻 \(-1\)

題目分析

考慮 \(p=(u, v)\) 完全包含 \(p'=(u', v')\) 的充要條件。不妨跑出每個節點的 dfn:\(L_u, R_u\),並且令 \(L_u \leq L_v\)\(L_{u'} \leq L_{v'}\)。分類討論一下 \(p'\) 的形態:

  1. \(\operatorname{lca}(u', v') \not \in \{u', v'\}\),即 \(p'\) 為折鏈。
    發現 \(u\) 需要在 \(u'\) 的子樹裡,\(v\)\(v'\) 的子樹裡。可以用 dfn 判斷子樹包含關係,即 \(u \in \operatorname{subtree}(v) \Leftrightarrow L_u \in [L_v, R_v]\)
  2. \(\operatorname{lca}(u', v') = u' \in \{u', v'\}\),即 \(p'\) 為直鏈。
    那麼需要 \(p\) 的其中一端在 \(v'\) 的子樹裡。對於另一端的限制,我們首先找到 \(u'\) 的一個孩子 \(w\),滿足 \(v' \in \operatorname{subtree}(w)\),那麼另一端需要在除了 \(\operatorname{subtree}(w)\) 的結點中。我們可以畫圖幫助理解:
    DFN 序列
    \(p\) 的一端在藍色部分中,另一端在綠色部分中。我們欽定了 \(L_u \leq L_v\),所以這裡可以分成不重複的兩部分:\(L_u \in [1, L_w) \land L_v \in [L_{v'}, R_{v'}]\)\(L_u \in [L_{v'}, R_{v'}] \land L_v \in (R_w, n]\)

於是,我們發現一條 \(S\) 中的路徑對之後查詢產生的貢獻,可以表示為 \(L_u\) 上一段區間和 \(L_v\) 上一段區間。將二元組放到二維笛卡爾座標系上,將問題轉化為了如下形式:

動態往平面裡插入帶權矩形,查詢包含某一個點的矩形權值和。

對於靜態問題,直接掃描線。此題使用樹套樹,或者 CDQ 分治。可不要小瞧樹套樹!要是強制線上,只能老老實實做資料結構嘍。

程式碼

樹套樹
CDQ 分治
#pragma GCC optimize("Ofast", "inline", "omit-frame-pointer", "no-stack-protector", "fast-math", "unroll-loops")

#include <cstdio>
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

const int MAX = 1 << 26;
char buf[MAX], *ip = buf, obuf[MAX], *op = obuf;
#define putchar(x) *op++ = x
template <typename T>
inline void read(T &x) {
    x = 0; char ch = *ip++;
    for (; ch <  48; ch = *ip++);
    for (; ch >= 48; ch = *ip++) x = (x << 3) + (x << 1) + (ch ^ 48);
}
template <typename T>
inline void write(T x) {
    static short stack[20], top(0);
    do stack[++top] = x % 10; while (x /= 10);
    while (top) putchar(stack[top--] | 48);
}

const int  N  = 100005;
const int lgN = __lg(N) + 1;

int n, m, q;
vector<int> edge[N];

int dpt[N], fa[N], st[lgN][N];
int L[N], R[N], timer;
void dfs(int u) {
    st[0][L[u] = ++timer] = u;
    for (int v : edge[u]) if (v != fa[u]) dpt[v] = dpt[u] + 1, fa[v] = u, dfs(v);
    R[u] = timer;
}
inline int Min(int u, int v) { return dpt[u] < dpt[v] ? u : v; }
// when dpt[u] == dpt[v], returns v
inline int plca(int u, int v) {
    // requires L[u] < L[v], u != v
    int p = __lg((v = L[v]) - (u = L[u])++);
    return Min(st[p][u], st[p][v - (1 << p) + 1]);
}

struct Line {
    int y, lx, rx, v;
} line[N << 3];
int lineCnt, qryCnt, ans[N];
inline void insert(int Lu, int Ru, int Lv, int Rv) {
    if (Lu > Ru || Lv > Rv) return;
    line[++lineCnt] = { Lv,     Lu, Ru,  1 };
    line[++lineCnt] = { Rv + 1, Lu, Ru, -1 };
}
inline void pathInsert(int u, int v) {
    if (L[u] > L[v]) u ^= v ^= u ^= v;
    int fp = plca(u, v), p = fa[fp];
    if (u == p) {
        // straight path
        insert(1, L[fp] - 1, L[v], R[v]);
        insert(L[v], R[v], R[fp] + 1, n);
    } else {
        // common path
        insert(L[u], R[u], L[v], R[v]);
    }
}
inline void qryInsert(int idx, int u, int v) {
    if (L[u] > L[v]) u ^= v ^= u ^= v;
    line[++lineCnt] = { L[v], L[u], idx, 0 };
}

int tree[N];
inline void modify(int p, int v) { for (; p <= n; p += p & -p) tree[p] += v; }
inline int query(int p) {
    int res = 0;
    for (; p; p &= p - 1) res += tree[p];
    return res;
}
inline void modify(int l, int r, int v) { modify(l, v), modify(r + 1, -v); }
void solve(int l, int r) {
    if (l == r) return;
    int mid = (l + r) >> 1;
    solve(l, mid), solve(mid + 1, r);
    int p = l - 1;
    for (int q = mid + 1; q <= r; ++q) {
        while (p + 1 <= mid && line[p + 1].y <= line[q].y)
            if (line[++p].v) modify(line[p].lx, line[p].rx, line[p].v);
        if (line[q].v == 0) ans[line[q].rx] += query(line[q].lx);
    }
    for (; p >= l; --p) if (line[p].v) modify(line[p].lx, line[p].rx, -line[p].v);
    inplace_merge(line + l, line + mid + 1, line + r + 1,
        [] (const Line& a, const Line& b) -> bool {
            return a.y < b.y;
        }
    );
}
// divide         solved time order
// inplace_merge  solved Y order
// data structure solved X order

signed main() {
    #ifndef XuYueming
    freopen("revolt.in", "r", stdin);
    freopen("revolt.out", "w", stdout);
    #endif
    fread(buf, 1, MAX, stdin), read(n), read(m), read(q);
    for (int i = 1, u, v; i < n; ++i) {
        read(u), read(v);
        edge[u].emplace_back(v);
        edge[v].emplace_back(u);
    }
    dfs(1);
    for (int k = 1; k < lgN; ++k)
        for (int i = 1; i + (1 << k) - 1 <= n; ++i)
            st[k][i] = Min(st[k - 1][i], st[k - 1][i + (1 << (k - 1))]);
    for (int u, v; m--; ) read(u), read(v), pathInsert(u, v);
    for (int op, u, v; q--; ) {
        read(op), read(u), read(v);
        if (op == 1) qryInsert(++qryCnt, u, v);
        else pathInsert(u, v);
    }
    solve(1, lineCnt);
    for (int i = 1; i <= qryCnt; ++i) write(ans[i]), putchar('\n');
    fwrite(obuf, 1, op - obuf, stdout);
    return 0;
}

相關文章