考場上感覺是一道可做題,然后最后做法差不多,但是一直卡在 \(dfn\),沒去搞 \(bfn\)。
然后今天撿起這道題想了一下就想出來了。
因為沒有交題鏈接,下面的做法不一定正確。(結論經過對拍,應該沒問題)
這是一個倆 \(\log\) 做法,不知道有沒有更優的。
首先,這是一個區間點構成虛樹的 \(K\) 連通塊個數。其中 \(K\) 連通為當在原樹上距離 \(\leq K\) 就加一條無向邊。
眾所周知,虛樹森林的連通塊個數可以由 \(\text{點數} - \text{邊數}\) 得到。
所以可以得到一個 \(trival\) 的 \(O(n^2Q)\) 的做法,即暴力連邊,如果距離 \(\leq K\) 就並查集連邊。
但是顯然過不了。
我們考慮縮小邊的數量。具體的,如果能給每個點欽點一個必連的點(比如說貪心的在左邊選一個最大的,在右邊選一個最小的),然后做一個高維數點,那就優美了。
對於詢問,給定點集,我們欽定點集中任意一個點 \(u\),對原樹 \(makeroot(u)\),那么剩下點集中的點 \(v\),如果它最近的祖先 \(a\) 滿足 \(dis(v, a) \leq K\),那么連一條有向邊 \(a \rightarrow v\)。
顯然,對於這個點集, \(u\) 的 \(K\) 連通塊集合 \(S\) 滿足對於所有點 \(v \in S\), \(u\) 能到達 \(v\)。
然而實際上 \(v\) 可以和任意一個滿足距離限制條件的祖先 \(a\) 匹配,並不需要最近的。這樣也是滿足這個性質的。
那么,我們記一個 \(Ans = r - l + 1\) 作為初值,每次找集合 \(S\) 中的一個元素 \(u\) 作為根,並進行連邊。記 \(u\) 能到達的除 \(u\) 外的點的集合為 \(A\),那么更新答案 \(Ans' = Ans - |A|\) ,更新集合 \(S' = S \backslash (A \cup \{u\})\)。
即每次更新完刪除根的連通塊。考慮這個過程,我們可以找到一個不變的關系:
如果每次都選 \(bfs\) 序最小的點更新,那么:
- 對於每次更新的連通塊,我們連的邊 \((u, v)\) 有 \(bfn_u < bfn_v\)。
- 對於根為 \(u\) 的一個連通塊更新完后,接下來對於未被更新的 \(v\) 且 \(v\) 在 \(u\) 子樹中,\(bfn_v\) 比這個連通塊所有點的 \(bfn\) 都大。
這樣,如果每個點只向 \(bfn\) 比它小的點連邊,連通性不變。那么我們的答案就是 \(\text{點數} - \text{這張圖的邊數}\)。
實際上,對於任意一個 \(K\),對距離 \(\leq K\) 的點之間連邊,得到的是一個弦圖 \(G\)。
一個經典的結論就是:對於一棵樹,以及任意一個這樣的 \(K\),用 \(bfs\) 得到的序列,就是 \(G\) 的完美消除序列。這也就印證了上面的那個性質。
現在問題轉化為:
對於一個區間 \([l, r]\),計算 \(u\) 的個數使得 \(l \leq u \leq r\) 且滿足(存在一個不同於 \(u\) 的 \(l \leq v \leq r\),使得 (\(bfn_v < bfn_u\)))。
顯然,對於 \(u\) 只要選擇 \(bfn_v\) 小於自己的 \(v\) 就行了。
顯然是選編號差 \(|u - v|\) 最小的更好,可以更方便的統計。
所以現在就有了一個 \(O(n^2 + nQ)\) 的做法:
對每個點 \(u\),預處理 \(v < u\) 且滿足 \(dis(u, v) \leq K, bfn_v < bfn_u\) 的最大的 \(v\),記為 \(L_u\)。如果不存在記 \(L_u = 0\)。
同理預處理 \(v > u\) 且滿足 \(dis(u, v) \leq K, bfn_v < bfn_u\) 的最小的 \(v\),記為 \(R_u\)。如果不存在記 \(R_u = n + 1\)。
於是只需要求
顯然 \(\lor\) 是可以轉變成 \(1 - \land\),所以就要計算:
放到樹上,也就是沒有父親的點個數。
我們先計算出 \(L\) 和 \(R\)。
不失一般性地考慮算 \(L\),對於 \(u\),我們需要計算滿足:
- \(dis(u, v) \leq K\)
- \(bfn_v \leq bfn_u\)
- \(v < u\)
的最大的 \(v\)。
首先,可以通過樹分治或 dsu 滿足條件 \(1\)。
然后對於條件 \(2\) 和 \(3\),可以使用樹狀數組套 \(set\),即支持前綴加元素,詢問前綴的元素集合里元素的前驅。
因此可以輕松地做到 \(O(n\log^3 n)\)。
然而這太慢了。
使用樹分治,考慮合並時,因為 \(LCA\) 固定,把 \(dis\) 拆成 \(dep\) 的限制。
使用雙指針,把 \(dep\) 從小到大掃過去。此時要往集合動態插入元素。
維護一個序列數據結構,維護 \(v = v'\) 時最小的滿足條件的 \(dfn\),支持后綴元素取 \(min\),查詢區間元素最小值,前綴單 \(log\) 二分。
因為前綴最小值是單調減的,所以二分正確性顯然。當然這實際上就是一個單調棧。
這樣就可以做到 \(O(n\log^2 n)\) 了。
計算出了 \(L\) 和 \(R\),還有計算答案時。顯然是一個三維數點。我們將詢問拆成兩部分。
那么要詢問滿足如下條件的 \(v\) 的個數:
- \(v < A\)
- \(L_v < B\)
- \(R_v > C\)
離線加樹套樹或使用分治解決。
復雜度 \(O(n \log^2 n)\)
綜上,時間復雜度 \(O(n \log^2 n)\),空間復雜度可以做到 \(O(n)\)。
#include <bits/stdc++.h>
const int MAXN = 300010;
int n, Q, K;
int head[MAXN], to[MAXN << 1], nxt[MAXN << 1], tot;
void addedge(int b, int e) {
nxt[++tot] = head[b]; to[head[b] = tot] = e;
nxt[++tot] = head[e]; to[head[e] = tot] = b;
}
int bfn[MAXN], dep[MAXN], buc[MAXN];
void dfs0(int u, int fa = 0) {
++buc[dep[u]];
for (int i = head[u]; i; i = nxt[i])
if (to[i] != fa)
dep[to[i]] = dep[u] + 1, dfs0(to[i], u);
}
int sz[MAXN], vis[MAXN], rt, rtv, all;
void getroot(int u, int fa = 0) {
sz[u] = 1;
int maxd = 0;
for (int i = head[u]; i; i = nxt[i])
if (!vis[to[i]] && to[i] != fa) {
getroot(to[i], u);
sz[u] += sz[to[i]];
maxd = std::max(maxd, sz[to[i]]);
}
maxd = std::max(maxd, all - sz[u]);
if (maxd < rtv) rtv = maxd, rt = u;
}
void _getroot(int u, int d) {
rt = u, all = d, rtv = 0x3f3f3f3f, getroot(u);
}
typedef std::vector<int> vi;
typedef std::pair<int, int> pi;
typedef std::vector<pi> vpi;
vpi tree[MAXN];
struct cmp {
bool operator () (int a, int b) {
return tree[a].size() > tree[b].size();
}
} ;
std::priority_queue<int, vi, cmp> q;
void calc(int u, vpi & tar, int dep = 1, int fa = 0) {
tar.emplace_back(dep, u);
for (int i = head[u]; i; i = nxt[i])
if (to[i] != fa && !vis[to[i]])
calc(to[i], tar, dep + 1, u);
}
int L[MAXN], R[MAXN];
const int INF = 0x3f3f3f3f;
int sgt[MAXN << 2];
void mdf(int tar, int v) {
int u = 1, l = 1, r = n;
while (l < r) {
sgt[u] = std::min(sgt[u], v);
int mid = l + r >> 1;
if (tar <= mid) u = u << 1, r = mid;
else u = u << 1 | 1, l = mid + 1;
}
sgt[u] = std::min(sgt[u], v);
}
void clr(int tar) {
int u = 1, l = 1, r = n;
while (l < r) {
sgt[u] = INF;
int mid = l + r >> 1;
if (tar <= mid) u = u << 1, r = mid;
else u = u << 1 | 1, l = mid + 1;
}
sgt[u] = INF;
}
int qryl(int R, int v, int u = 1, int l = 1, int r = n) {
if (sgt[u] > v) return 0;
if (r <= R) {
while (l < r) {
int mid = l + r >> 1;
if (sgt[u << 1 | 1] <= v) u = u << 1 | 1, l = mid + 1;
else u = u << 1, r = mid;
}
return l;
}
int mid = l + r >> 1;
if (R > mid) if (int t = qryl(R, v, u << 1 | 1, mid + 1, r)) return t;
return qryl(R, v, u << 1, l, mid);
}
int qryr(int L, int v, int u = 1, int l = 1, int r = n) {
if (sgt[u] > v) return 0;
if (L <= l) {
while (l < r) {
int mid = l + r >> 1;
if (sgt[u << 1] <= v) u = u << 1, r = mid;
else u = u << 1 | 1, l = mid + 1;
}
return l;
}
int mid = l + r >> 1;
if (L <= mid) if (int t = qryr(L, v, u << 1, l, mid)) return t;
return qryr(L, v, u << 1 | 1, mid + 1, r);
}
void solve(int u) {
vis[u] = true; int bak;
tree[bak = 1].emplace_back(0, u);
for (int i = head[u]; i; i = nxt[i]) if (!vis[to[i]])
calc(to[i], tree[++bak]);
for (int i = 1; i <= bak; ++i) q.push(i);
auto make = [] (vpi & x, vpi & y) {
int cur = 0, SZ = x.size();
std::reverse(y.begin(), y.end());
for (auto t : y) {
while (cur < SZ && x[cur].first + t.first <= K) {
int u = x[cur].second;
mdf(u, bfn[u]);
++cur;
}
int u = t.second, atl = qryl(u, bfn[u]), atr = qryr(u, bfn[u]);
if (atl < u) L[u] = std::max(L[u], atl);
if (atr > u) R[u] = std::min(R[u], atr);
}
for (int i = 0; i < cur; ++i) clr(x[i].second);
std::reverse(y.begin(), y.end());
};
while (q.size() > 1) {
int x = q.top(); q.pop();
int y = q.top(); q.pop();
std::sort(tree[x].begin(), tree[x].end());
std::sort(tree[y].begin(), tree[y].end());
make(tree[x], tree[y]); make(tree[y], tree[x]);
if (tree[x].size() < tree[y].size()) std::swap(x, y);
for (auto t : tree[y]) tree[x].push_back(t);
tree[y].clear();
q.push(x);
}
while (!q.empty()) q.pop();
for (int i = 1; i <= bak; ++i) tree[i].clear();
for (int i = head[u]; i; i = nxt[i])
if (!vis[to[i]]) {
getroot(to[i]);
_getroot(to[i], sz[to[i]]);
solve(rt);
}
}
inline int absx(int x) { return x < 0 ? -x : x ; }
namespace divide {
struct qry {
int typ, a, b, c;
} qs[MAXN * 5];
int bak, ansl[MAXN * 2], tree[MAXN];
void solve(qry * l, qry * r) {
if (l + 1 >= r) return ;
qry * mid = l + (r - l >> 1);
solve(l, mid); solve(mid, r);
qry * lhs = l, * rhs = mid;
while ((lhs < mid) || (rhs < r)) {
if (rhs == r || (lhs < mid && lhs -> b < rhs -> b)) {
if (lhs -> typ == 0) {
for (int x = lhs -> c; x; x &= x - 1) ++tree[x];
}
++lhs;
} else {
if (rhs -> typ != 0) {
int at = rhs -> typ;
int coef = absx(at) / at, res = 0;
at *= coef;
for (int x = rhs -> c + 1; x <= n + 1; x += x & -x) res += tree[x];
ansl[at] += coef * res;
}
++rhs;
}
}
for (qry * it = l; it != mid; ++it) if (it -> typ == 0)
for (int x = it -> c; x; x &= x - 1) --tree[x];
std::sort(l, r, [] (qry a, qry b) {
return a.b == b.b ? absx(a.typ) < absx(b.typ) : a.b < b.b;
});
}
void solve() {
std::sort(qs + 1, qs + 1 + bak, [] (qry a, qry b) {
return a.a == b.a ? absx(a.typ) < absx(b.typ) : a.a < b.a;
});
solve(qs + 1, qs + 1 + bak);
}
}
int main() {
std::ios_base::sync_with_stdio(false), std::cin.tie(0);
memset(sgt, 0x3f, sizeof sgt);
std::cin >> n >> Q >> K;
for (int i = 1; i <= n; ++i) R[i] = n + 1;
for (int i = 2, t; i <= n; ++i)
std::cin >> t, addedge(i, t);
dep[1] = 1; dfs0(1);
for (int i = 1; i <= n; ++i) buc[i] += buc[i - 1];
for (int i = n; i; --i) bfn[i] = buc[dep[i]]--;
_getroot(1, n); solve(rt);
for (int i = 1; i <= n; ++i)
divide::qs[++divide::bak] = (divide::qry) {0, i, L[i], R[i]};
for (int i = 1, l, r; i <= Q; ++i) {
std::cin >> l >> r;
divide::qs[++divide::bak] = (divide::qry) {i, r, l, r};
divide::qs[++divide::bak] = (divide::qry) {-i, l - 1, l, r};
}
divide::solve();
for (int i = 1; i <= Q; ++i)
std::cout << divide::ansl[i] << '\n';
return 0;
}