1. 程式人生 > 其它 >【luogu CF1137F】Matches Are Not a Child‘s Play

【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;
}