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