NOI2021 统一省选(A卷) Day2 T1 宝石(树上主席树+二分+倍增)

NOI2021 统一省选(A卷) Day2 T1 宝石

题目大意

  • 大小为 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,q2105m,P5104

题解

  • 有一档 m ≤ 300 m\le300 m300的部分分,可以直接记录每个点向上权值为 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>
using namespace std;
#define N 200010
#define M 50010
int 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];
void add(int x, int y) {
	to[++len] = y;
	nxt[len] = last[x];
	last[x] = len;
}
int find(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) return find(f[v].l, l, mid, x);
	}
	else {
		if(f[v].r) return find(f[v].r, mid + 1, r, x);
	}
	return 0;
}
void ins(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;
		}
	}
}
void dfs(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);
	
}
int lca(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];
}
int read() {
	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;
}
int main() {
	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);
	}
	return 0;
}

自我小结

  • 感觉这题全省排名靠前的都切穿了,要是切了直接全省前十。
  • 其实我把部分分都写完了,但是有25分好像带个log被卡了,还有20分官方数据出大锅?!
  • 这题这种解法和我的部分分其实很像的,最近树上主席树写的少了没有意识到,要是写了就算被卡常分数也不至于现在这么惨。
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值