Problem
n
n
个点的树,以1为根,给条路径。
每次询问给
v
v
、,求一个在满足u-v这条路径至少被
k
k
条前面给出的路径完全包含的条件下距离根最近的那个点。(输出
u−v
u
−
v
的距离)
Solution
由于查询路径只会是直上直下的,所以我们把每条路径拆成两段直上直下的。
对于一条路径 u−v u − v ( dep[u]≤dep[v] d e p [ u ] ≤ d e p [ v ] ,它被路径 x−y x − y ( dep[x]≤dep[y] d e p [ x ] ≤ d e p [ y ] )包含,当且仅当 y y 在的子树内且 dep[x]≤dep[u] d e p [ x ] ≤ d e p [ u ] 。而在子树中可以用 dfs d f s 序来表示。
所以我们对于一条路径 x−y x − y ,我们可以表示为 (dfn[y],dep[x]) ( d f n [ y ] , d e p [ x ] ) 这个点对。
这个二维的不带修改问题,我们可以使用主席树。外层
dfs
d
f
s
序,内层
dep
d
e
p
。
那么我们对于一个
u−v
u
−
v
,可以在
O(logn)
O
(
l
o
g
n
)
的时间得出有多少路径完全包含它。
但是 200000 200000 是可以卡掉大常数的 O(nlog2n) O ( n l o g 2 n ) 的。
我们把这个问题转化为求第k小。
对于一个 v v ,我们求出下端点在它子树内,且上端点深度第小的点,这就是我们的答案 u u 。此时前~ k k 名肯定都可以覆盖,并且这也是最靠近根的。
如果求出来的 u u 并不在的上面,说明无解。
时间复杂度 O(nlogn) O ( n l o g n ) 。
Code
#include <bits/stdc++.h>
using namespace std;
#define N 200010
inline char gc() {
static char now[1<<16], *S, *T;
if(S == T) {T = (S = now) + fread(now, 1, 1<<16, stdin); if(S == T) return EOF;}
return *S++;
}
inline int read() {
int x = 0, f = 1; char c = gc();
while(c < '0' || c > '9') {if(c == '-') f = -1; c = gc();}
while(c >= '0' && c <= '9') {x = (x << 3) + (x << 1) + c - 48; c = gc();}
return x * f;
}
struct adj {int to, nxt;}e[N<<1];
int n, m, hd[N], cnt = 1;
inline void addedge(int x, int y) {
e[++cnt] = (adj){y, hd[x]}; hd[x] = cnt;
}
int f[N][18], sz[N], dep[N], son[N], in[N], out[N], tim = 0;
void dfs1(int x) {
in[x] = ++tim;
for(int i = 1; i <= 17; ++i)
f[x][i] = f[f[x][i - 1]][i - 1];
sz[x] = 1;
for(int i = hd[x]; i; i = e[i].nxt) {
int y = e[i].to;
if(y == f[x][0]) continue;
dep[y] = dep[x] + 1;
f[y][0] = x;
dfs1(y);
sz[x]+= sz[y];
if(sz[y] > sz[son[x]]) son[x] = y;
}
out[x] = tim;
}
int tp[N];
void dfs2(int x, int Tp) {
tp[x] = Tp;
if(son[x]) dfs2(son[x], Tp);
for(int i = hd[x]; i; i = e[i].nxt) {
int y = e[i].to;
if(y != f[x][0] && y != son[x])
dfs2(y, y);
}
}
inline int LCA(int x, int y) {
while(tp[x] != tp[y]) {
if(dep[tp[x]] < dep[tp[y]]) swap(x, y);
x = f[tp[x]][0];
}
return (dep[x] < dep[y]) ? x : y;
}
struct item {int x, y;}soldier[N<<1];
inline bool operator < (const item &A, const item &B) {
return in[A.x] < in[B.x];
}
int rt[N], L[N * 360], R[N * 360], v[N * 360], len = 0;
void add(int pre, int &p, int l, int r, int x) {
p = ++len; v[p] = v[pre] + 1;
if(l == r) return ;
int mid = (l + r)>>1;
if(x <= mid) R[p] = R[pre], add(L[pre], L[p], l, mid, x);
else L[p] = L[pre], add(R[pre], R[p], mid + 1, r, x);
}
int query(int A, int B, int l, int r, int k) {
if(l == r) return l;
int mid = (l + r)>>1;
if(v[L[A]] - v[L[B]] >= k) return query(L[A], L[B], l, mid, k);
else return query(R[A], R[B], mid + 1, r, k - v[L[A]] + v[L[B]]);
}
int main() {
n = read(); m = read();
memset(hd, 0, sizeof(hd));
for(int i = 1; i < n; ++i) {
int x = read(), y = read();
addedge(x, y);
addedge(y, x);
}
dep[1] = 1; dfs1(1); dfs2(1, 1);
for(int i = 1; i <= m; ++i) {
int x = read(), y = read(), z = LCA(x, y);
soldier[2 * i - 1] = (item){x, z};
soldier[2 * i] = (item){y, z};
}
sort(soldier+1, soldier+2*m+1);
for(int i = 1; i <= 2 * m; ++i) {
for(int j = in[soldier[i - 1].x] + 1; j < in[soldier[i].x]; ++j) rt[j] = rt[in[soldier[i - 1].x]];
add(rt[in[soldier[i - 1].x]], rt[in[soldier[i].x]], 1, n, dep[soldier[i].y]);
}
for(int i = in[soldier[2 * m].x] + 1; i <= n; ++i) rt[i] = rt[in[soldier[2 * m].x]];
int q = read();
for(int i = 1; i <= q; ++i) {
int x = read(), k = read();
if(v[rt[out[x]]] - v[rt[in[x] - 1]] < k) {puts("0"); continue;}
int ans = query(rt[out[x]], rt[in[x] - 1], 1, n, k);
printf("%d\n", max(0, dep[x] - ans));
}
return 0;
}