【luogu P6779】rla1rmdq(分块)(树链剖分)

rla1rmdq

题目链接:luogu P6779

题目大意

给你一个 n 个点的有根树,根给出,和一个值域在 1~n 的数组。
然后 m 次操作,每次对于一个数组的区间 l~r,把它们的值都变成格子树上父亲的编号(如果是已经根就不变),或者求它们到根节点的路径长度的最小值。

思路

考虑分块。

着重看大块的修改和查询。
考虑全局修改和全局查询。

发现因为是全部一起往上蹦,所以如果一个点蹦到了别的点曾经到过的,那它没必要再蹦了。
(永远有它的祖先比它更好)
那所以我们可以暴力蹦,然后暴力删,复杂度是 \(O(n+q)\) 的。

那么考虑到分块上,那我们考虑对于每一大块,算他的贡献。
那我们记录一个答案是整个大块的答案,然后大块修改就像我上面说的暴力改。

接着就是小块,先看修改,会发现一个问题。
就是它可能原本是被删掉的,然后它自己偷偷蹦上去了。
那你就 \(O(1)\) 把它补上就好。

那询问的话,你考虑弄一个 \(tag\) 表示整体加了多少次,再用一个 \(val_i\) 表示数组 \(i\) 这个位置的跳了多少次(如果被删掉了就不会加)
那它的真实位置就是它的 \(tag-val_i\) 级祖先,我们就跳到那里,然后把 \(val_i=tag\) 就可以。
那如果它跳到了一个没有人到过的位置,那它就重新有效。

然后询问你也跳到真实位置然后贡献就可以啦。

然后至于怎么找 \(k\) 级祖先,直接树链剖分搞(轻重链就可以,没必要长链,你分块都已经带 \(\log\) 了)

代码

#include<cmath>
#include<cstdio>
#include<iostream>
#define ll long long 

using namespace std;

const int N = 2e5 + 100;
struct node {
	int x, to, nxt;
}e[N << 1];
int n, m, rt, le[N], KK, B, a[N];
int bl[N], br[N], blo[N], Bn;
struct Quest {
	int op, l, r;
}q[N];
ll ans[N];

void add(int x, int y, int z) {e[++KK] = (node){z, y, le[x]}; le[x] = KK;}

int fa[N], d[N]; ll deg[N]; 
struct SLPF {
	int sz[N], son[N], top[N], dfn[N], rnk[N];
	
	void dfs0(int now, int father) {
		fa[now] = father; sz[now] = 1;
		for (int i = le[now]; i; i = e[i].nxt)
			if (e[i].to != father) {
				deg[e[i].to] = deg[now] + e[i].x;
				d[e[i].to] = d[now] + 1;
				dfs0(e[i].to, now); sz[now] += sz[e[i].to];
				if (sz[e[i].to] > sz[son[now]]) son[now] = e[i].to;
			}
	}
	
	void dfs1(int now, int father) {
		dfn[now] = ++dfn[0]; rnk[dfn[0]] = now;
		if (son[now]) {
			top[son[now]] = top[now]; dfs1(son[now], now);
		}
		for (int i = le[now]; i; i = e[i].nxt)
			if (e[i].to != father && e[i].to != son[now]) {
				top[e[i].to] = e[i].to;
				dfs1(e[i].to, now);
			}
	}
	
	void Init() {
		dfs0(rt, 0);
		top[rt] = rt; dfs1(rt, 0);
	}
	
	int ask_k(int now, int k) {
		if (k >= d[now]) return rt;
		while (d[now] - d[top[now]] < k) {
			k -= d[now] - d[top[now]] + 1;
			now = fa[top[now]];
		}
		return rnk[dfn[now] - k];
	}
}T;

int val[N], sta[N];
bool in[N], met[N];

void slove(int L, int R) {
	for (int i = 1; i <= n; i++)
		val[i] = sta[i] = in[i] = met[i] = 0;
	ll an = 1e18; int tot = 0, tag = 0;
	for (int i = L; i <= R; i++) {
		if (met[a[i]]) continue;
		met[a[i]] = 1; in[i] = 1;
		sta[++tot] = i; an = min(an, deg[a[i]]);
	}
	for (int i = 1; i <= m; i++) {
		int l = max(L, q[i].l), r = min(R, q[i].r);
		if (l > r) continue;
		if (q[i].op == 1) {
			if (L == l && r == R) {
				int tmp = tot; tot = 0; tag++;
				for (int j = 1; j <= tmp; j++) {
					val[sta[j]]++; a[sta[j]] = fa[a[sta[j]]];
					if (met[a[sta[j]]]) in[sta[j]] = 0;
						else {
							met[a[sta[j]]] = 1;
							sta[++tot] = sta[j];
							an = min(an, deg[a[sta[j]]]);
						}
				}
			}
			else {
				for (int j = l; j <= r; j++) {
					a[j] = T.ask_k(a[j], (tag - val[j]) + 1); val[j] = tag;
					if (!met[a[j]]) {
						met[a[j]] = 1;
						if (!in[j]) sta[++tot] = j, in[j] = 1;
						an = min(an, deg[a[j]]);
					}
				}
			}
		}
		if (q[i].op == 2) {
			if (L == l && r == R) ans[i] = min(ans[i], an);
				else {
					for (int j = l; j <= r; j++) {
						a[j] = T.ask_k(a[j], tag - val[j]); val[j] = tag;
						ans[i] = min(ans[i], deg[a[j]]);
					}
				} 
		}
	}
}

int main() {
	scanf("%d %d %d", &n, &m, &rt);
	for (int i = 1; i < n; i++) {
		int x, y, z; scanf("%d %d %d", &x, &y, &z);
		add(x, y, z); add(y, x, z);
	}
	for (int i = 1; i <= n; i++) scanf("%d", &a[i]);
	T.Init();
	
	B = sqrt(n);
	for (int i = 1; i <= n; i++) {
		blo[i] = (i - 1) / B + 1;
		if (!bl[blo[i]]) bl[blo[i]] = i;
		br[blo[i]] = i;
	}
	Bn = blo[n];
	
	for (int i = 1; i <= m; i++) {
		int op, l, r; scanf("%d %d %d", &op, &l, &r);
		q[i] = (Quest){op, l, r}; ans[i] = 1e18;
	}
	for (int i = 1; i <= Bn; i++) slove(bl[i], br[i]);
	
	for (int i = 1; i <= m; i++)
		if (q[i].op == 2) printf("%lld\n", ans[i]);
	
	return 0;
}
posted @ 2022-09-30 13:19  あおいSakura  阅读(36)  评论(0编辑  收藏  举报