大小为
n
n
n的树上,每个点有一个权值
w
i
≤
[
1
,
m
]
w_i\le[1,m]
wi≤[1,m],给出一个无重序列
P
P
P,
q
q
q组询问,每次求从
x
x
x到
y
y
y的最短路径的点权能从
1
1
1开始对应序列
P
P
P的多少位。
n
,
q
≤
2
∗
1
0
5
,
m
,
∣
P
∣
≤
5
∗
1
0
4
n,q\le2*10^5,m,|P|\le5*10^4
n,q≤2∗105,m,∣P∣≤5∗104
题解
有一档
m
≤
300
m\le300
m≤300的部分分,可以直接记录每个点向上权值为
i
i
i的点是哪个,然后从起点贪心向上跳,从终点二分后贪心向上跳,时间复杂度
O
(
q
m
log
2
m
)
O(qm\log_2 m)
O(qmlog2m),比赛时被卡了没拿到这档分。
但由此可以想到更快的做法,首先当
m
m
m特别大时不能用数组直接记录,因为是在树上且每次由父亲转移来并修改,所以可以用树上主席树记录。
同时,二分后也不能一个一个暴力跳,直接考虑倍增。使用两个倍增数组分别记录从每个点开始按
P
P
P序列中当前点权值位置开始正着和反着跳
2
i
2^i
2i到达的点,需要先在主席树中查询出起点向上的第一个
P
1
P_1
P1和终点向上第一个
P
m
i
d
P_{mid}
Pmid的位置。
时间复杂度
O
(
q
log
2
2
m
)
O(q\log^2_2m)
O(qlog22m),理论可过,实际常数较大,视评测环境而定(事实跑的极慢)。
代码
#include<cstdio>#include<cstring>#include<algorithm>usingnamespace std;#define N 200010#define M 50010int n, m, C, P[M], pr[M], a[N];int last[N], nxt[N *2], to[N *2], len =0;int tot =0, h[N], dp[N];int F[N][16], G[N][16], H[N][18];struct{int l =0, r =0, x;}f[N *20];voidadd(int x,int y){
to[++len]= y;
nxt[len]= last[x];
last[x]= len;}intfind(int v,int l,int r,int x){if(l == r)return f[v].x;int mid =(l + r)/2;if(x <= mid){if(f[v].l)returnfind(f[v].l, l, mid, x);}else{if(f[v].r)returnfind(f[v].r, mid +1, r, x);}return0;}voidins(int v,int v0,int l,int r,int x,int c){if(l == r) f[v].x = c;else{int mid =(l + r)/2;if(x <= mid){if(!f[v].l) f[v].l =++tot;ins(f[v].l, f[v0].l, l, mid, x, c);
f[v].r = f[v0].r;}else{if(!f[v].r) f[v].r =++tot;ins(f[v].r, f[v0].r, mid +1, r, x, c);
f[v].l = f[v0].l;}}}voiddfs(int k,int fa){
h[k]=++tot;ins(h[k], h[fa],1, m, a[k], k);if(pr[a[k]]!= C) F[k][0]=find(h[k],1, m, P[pr[a[k]]+1]);for(int i =1; i <16&& F[k][i -1]; i++) F[k][i]= F[F[k][i -1]][i -1];if(pr[a[k]]!=1) G[k][0]=find(h[k],1, m, P[pr[a[k]]-1]);for(int i =1; i <16&& G[k][i -1]; i++) G[k][i]= G[G[k][i -1]][i -1];
H[k][0]= fa;for(int i =1; i <18&& H[k][i -1]; i++) H[k][i]= H[H[k][i -1]][i -1];
dp[k]= dp[fa]+1;for(int i = last[k]; i; i = nxt[i])if(to[i]!= fa)dfs(to[i], k);}intlca(int x,int y){if(dp[x]< dp[y])swap(x, y);for(int i =17; i >=0; i--)if(dp[x]-(1<< i)>= dp[y]) x = H[x][i];if(x == y)return x;for(int i =17; i >=0; i--)if(H[x][i]!= H[y][i]) x = H[x][i], y = H[y][i];return H[x][0];}intread(){int s =0;char x =getchar();while(x <'0'|| x >'9') x =getchar();while(x >='0'&& x <='9') s = s *10+ x -48, x =getchar();return s;}intmain(){int i, x, y;
n =read(), m =read(), C =read();for(i =1; i <= C; i++) P[i]=read(), pr[P[i]]= i;for(i =1; i <= n; i++) a[i]=read();for(i =1; i < n; i++){
x =read(), y =read();add(x, y),add(y, x);}dfs(1,0);int Q =read();while(Q--){
x =read(), y =read();int L =lca(x, y), t;
x =find(h[x],1, m, P[1]);if(dp[x]< dp[L]) t =0;else{
t =1;for(i =15; i >=0&& dp[x]>= dp[L]; i--){if(dp[F[x][i]]>= dp[L]) x = F[x][i], t +=1<< i;}}int l = t, r = C, ans = t, y0 = y;while(l <= r){int mid =(l + r)/2, s;
y =find(h[y0],1, m, P[mid]);if(dp[y]< dp[L]) s = mid +1;else{
s = mid;for(i =15; i >=0&& dp[y]>= dp[L]; i--){if(dp[G[y][i]]>= dp[L]) y = G[y][i], s -=1<< i;}}if(s <= t +1) ans = mid, l = mid +1;else r = mid -1;}printf("%d\n", ans);}return0;}