考场上感觉是一道可做题,然后最后做法差不多,但是一直卡在 \(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;
}