【luogu CF1137F】Matches Are Not a Child‘s Play

Matches Are Not a Child's Play

题目链接:luogu CF1137F

题目大意

定义一个树的序列是每次把权值最小叶节点删去,这个删去的顺序序列。
然后给你一个树,要你维护三个操作:
把一个点的权值改成当前树最大权值+1,求一个点在这个序列中的位置,比较两个点在这个序列中谁更靠前。

思路

易得第三个问题是来搞笑的,搞出第二个问题就行。

首先我们考虑求出了一开始的删除序列(这个好求),然后进行修改会怎么变动。
那你会发现搞到最后会剩下一条链,就是最大和第二大为两端的链。
然后对于这条链,它会从第二大那一段开始删,一直删,删到最大那边。
那你发现这段有序,考虑把这一条链提取出来,用 LCT。

那你考虑把它染成最深的颜色。
至于怎么染色,就是搞一个懒标记,它的颜色传给它的儿子。
那我们想到就分出了虚实边,实边是颜色相同的,虚边是颜色不同的。

它看似要提取链,但其实我们不用,一开始我们建树以 \(n\) 为根,接着每次就把要修改的点 \(i\) 弄成根,那每次到根节点的 access 路径就是我们要的路径了。

那我们考虑对于一个点的删除序列中位置,就是权值比它小的个数加上它所在实链上颜色比它深的个数加一。

那对于求权值比它小的,我们可以用一个树状数组来搞。
那我们 access 修改的时候,我们就要先维护一个 \(sz\) 代表它实子树大小,然后每次减去之前的颜色,加上新的颜色。
然后置于它所在实链上颜色比它深的,我们就直接反应为在平衡树上它右子树的个数。

然后就可以了,具体的实现可以看看代码。

代码

#include<cstdio>
#include<algorithm>

using namespace std;

struct node {
	int to, nxt;
}e[400001];
int n, q, l[200001], r[200001], tot;
int x, y, fa[200001], sz[200001];
int tree[400001], col[200001];
int le[200001], KK;
bool lzs[200001];
char op;

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

//树状数组
void add_(int x, int y) {
	for (; x <= n + q; x += x & (-x))//记得要预留好新开的颜色位置
		tree[x] += y;
}

int query_(int x) {
	int re = 0;
	for (; x; x -= x & (-x))
		re += tree[x];
	return re;
}

//LCT
bool nrt(int now) {
	return l[fa[now]] == now || r[fa[now]] == now;
}

bool ls(int now) {
	return l[fa[now]] == now;
}

void up(int now) {
	sz[now] = sz[l[now]] + sz[r[now]] + 1;
}

void downs(int now) {
	lzs[now] ^= 1;
	swap(l[now], r[now]);
}

void down(int now) {
	if (l[now]) col[l[now]] = col[now];//颜色的传递
	if (r[now]) col[r[now]] = col[now];
	if (lzs[now]) {
		if (l[now]) downs(l[now]);
		if (r[now]) downs(r[now]);
		lzs[now] = 0;
	}
}

void down_line(int now) {
	if (nrt(now)) down_line(fa[now]);
	down(now);
}

void rotate(int x) {
	int y = fa[x];
	int z = fa[y];
	int b = (ls(x) ? r[x] : l[x]);
	if (z && nrt(y)) (ls(y) ? l[z] : r[z]) = x;
	if (ls(x)) r[x] = y, l[y] = b;
		else l[x] = y, r[y] = b;
	fa[x] = z;
	fa[y] = x;
	if (b) fa[b] = y;
	
	up(y);
}

void Splay(int x) {
	down_line(x);
	
	while (nrt(x)) {
		if (nrt(fa[x])) {
			if (ls(x) == ls(fa[x])) rotate(fa[x]);
				else rotate(x);
		}
		rotate(x);
	}
	
	up(x);
}

void access(int x) {
	int lst = 0;
	for (; x; x = fa[x]) {
		Splay(x);
		
		r[x] = 0;
		up(x);
		add_(col[x], -sz[x]);//把原来的颜色清掉
		add_(tot, sz[x]);//染上新的最大颜色
		
		r[x] = lst;
		up(x);
		lst = x;
	}
}

void make_root(int x) {
	tot++;//新开最大的颜色
	access(x);
	Splay(x);
	col[x] = tot;//只用标记最上面的,后面的当懒标记下传
	downs(x);
}

int query(int x) {
	Splay(x);
	return query_(col[x] - 1) + sz[r[x]] + 1;
}

void dfs(int now) {
	col[now] = now;
	for (int i = le[now]; i; i = e[i].nxt)
		if (!col[e[i].to]) {
			fa[e[i].to] = now;
			dfs(e[i].to);
			if (col[e[i].to] > col[now]) {//要删了它才能删儿子
				col[now] = col[e[i].to];
				r[now] = e[i].to;
			}
		}
	add_(col[now], 1);
	up(now);
}

int main() {
	scanf("%d %d", &n, &q);
	
	for (int i = 1; i < n; i++) {
		scanf("%d %d", &x, &y);
		add(x, y);
	}
	
	tot = n;
	dfs(n);
	
	for (int j = 1; j <= q; j++) {
		op = getchar();
		while (op != 'u' && op != 'w' && op != 'c') op = getchar();
		
		if (op == 'u') {
			for (int i = 1; i <= 1; i++) getchar();
			
			scanf("%d", &x);
			make_root(x);
			
			continue;
		}
		if (op == 'w') {
			for (int i = 1; i <= 3; i++) getchar();
			
			scanf("%d", &x);
			printf("%d\n", query(x));
			
			continue;
		}
		if (op == 'c') {
			for (int i = 1; i <= 6; i++) getchar();
			
			scanf("%d %d", &x, &y);
			printf("%d\n", (query(x) < query(y)) ? x : y);
			
			continue;
		}
	}
	
	return 0;
}
上一篇:【luogu P7599】雨林跳跃


下一篇:洛谷 P1351 联合权值