題意
有一棵 \(n\) 個節點且以 \(1\) 爲根的樹,把它複製成 \(m\) 個版本,有 \(q\) 次操作,每次對 \([l, r]\) 這些版本的 \(v\) 節點到根的路徑收縮起來。
收縮 \(v\) 也就是把 \(v\) 到根路徑上(除了根)所有點的父親都變成根。
最後查詢每個版本的每個點的 \(dep\) 之和。
數據範圍
\(n, m, q \le 2 \times 10^5\)
題解
操作順序是無所謂的,我們假設操作了點集 \(S\) ,那麼最後被縮上去的點其實就是 \(\{S, root\}\) 構成虛樹經過的節點。
每個點的深度其實它原來的深度減去它到根(除了根與根的兒子)被縮的點的個數。
考慮祖先對它的貢獻是比較麻煩的,我們不妨考慮它對於祖先的貢獻,其實就是每個深度 \(\ge 2\) 的節點的子樹 \(size\) 之和。
那麼我們把操作離線,只需要動態維護虛樹經過所有點的權值和。
這其實是一個經典的動態虛樹的問題,按照 \(dfs\) 序,用 std :: set
維護當前的點集,假設插入點爲 \(k\) 找到它的前驅 \(l\) 與後繼 \(r\) ,令 \(\mathrm{LCA}(l, k), \mathrm{LCA}(r, k)\) 深度較大點爲 \(f\) ,那麼這次新產生的路徑是 \((k, f)\) 的路徑(注意 \(f\) 原來就是存在於虛樹中的,需要去掉),刪除是類似的。
注意可能一個點被縮多次,我們需要利用
std :: multiset
,然後每次插入刪除的時候查找是否還存在即可。
複雜度是 \(\mathcal O((n + q) \log n + m)\) 的。
代碼
#include <bits/stdc++.h>
#define For(i, l, r) for (register int i = (l), i##end = (int)(r); i <= i##end; ++i)
#define Fordown(i, r, l) for (register int i = (r), i##end = (int)(l); i >= i##end; --i)
#define Rep(i, r) for (register int i = (0), i##end = (int)(r); i < i##end; ++i)
#define Set(a, v) memset(a, v, sizeof(a))
#define Cpy(a, b) memcpy(a, b, sizeof(a))
#define debug(x) cout << #x << ": " << (x) << endl
using namespace std;
typedef long long ll;
typedef pair<int, int> PII;
template<typename T> inline bool chkmin(T &a, T b) { return b < a ? a = b, 1 : 0; }
template<typename T> inline bool chkmax(T &a, T b) { return b > a ? a = b, 1 : 0; }
inline int read() {
int x(0), sgn(1); char ch(getchar());
for (; !isdigit(ch); ch = getchar()) if (ch == '-') sgn = -1;
for (; isdigit(ch); ch = getchar()) x = (x * 10) + (ch ^ 48);
return x * sgn;
}
void File() {
#ifdef zjp_shadow
freopen ("1954.in", "r", stdin);
freopen ("1954.out", "w", stdout);
#endif
}
const int N = 2e5 + 1e3;
vector<int> G[N];
ll ans = 0, sum[N];
int n, m, q, dep[N], anc[N][20], Log2[N], sz[N], dfn[N];
void Dfs_Init(int u, int fa = 0) {
static int clk = 0; dfn[u] = ++ clk;
dep[u] = dep[anc[u][0] = fa] + 1;
ans += dep[u]; sz[u] = 1;
for (int v : G[u]) if (v != fa) Dfs_Init(v, u), sz[u] += sz[v];
}
void Get_Sum(int u, int fa = 0) {
sum[u] = sum[fa] + (dep[u] > 1) * sz[u];
for (int v : G[u]) if (v != fa) Get_Sum(v, u);
}
struct Cmp {
inline bool operator () (const int &lhs, const int &rhs) { return dfn[lhs] < dfn[rhs]; }
};
vector<int> add[N], del[N]; multiset<int, Cmp> S;
inline int Lca(int x, int y) {
if (dep[x] < dep[y]) swap(x, y);
int gap = dep[x] - dep[y];
For (i, 0, Log2[gap])
if (gap >> i & 1) x = anc[x][i];
if (x == y) return x;
Fordown (i, Log2[dep[x]], 0)
if (anc[x][i] != anc[y][i]) x = anc[x][i], y = anc[y][i];
return anc[x][0];
}
int Find(int x) {
PII res; auto it = S.upper_bound(x);
if (it != S.end()) {
int tmp = Lca(*it, x); chkmax(res, {dep[tmp], tmp});
}
if (it != S.begin()) {
int tmp = Lca(*prev(it), x); chkmax(res, {dep[tmp], tmp});
}
return res.second ? res.second : x;
}
int main () {
File();
n = read(); m = read(); q = read();
For (i, 1, n - 1) {
int u = read(), v = read();
G[u].push_back(v); G[v].push_back(u);
}
while (q --) {
int l = read(), r = read(), v = read();
add[l].push_back(v); del[r + 1].push_back(v);
}
dep[0] = -1; Dfs_Init(1); Get_Sum(1);
For (i, 2, n) Log2[i] = Log2[i >> 1] + 1;
For (j, 1, Log2[n]) For (i, 1, n)
anc[i][j] = anc[anc[i][j - 1]][j - 1];
S.insert(1);
For (i, 1, m) {
for (int x : add[i]) {
if (S.find(x) == S.end())
ans -= sum[x] - sum[Find(x)];
S.insert(x);
}
for (int x : del[i]) {
S.erase(S.find(x));
if (S.find(x) == S.end())
ans += sum[x] - sum[Find(x)];
}
printf ("%lld%c", ans, i == iend ? '\n' : ' ');
}
return 0;
}