【luogu CF1137F】【YBT2023寒假Day9 C】Matches Are Not a Child‘s Play(LCT)(树状数组)

Matches Are Not a Child's Play

题目链接:luogu CF1137F / YBT2023寒假Day9 C

题目大意

定义一个树的序列是每次把权值最小叶节点删去,这个删去的顺序序列。
然后给你一个树,要你维护三个操作:
把一个点的权值改成当前树最大权值+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; }

__EOF__

本文作者あおいSakura
本文链接https://www.cnblogs.com/Sakura-TJH/p/luogu_CF1137F.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   あおいSakura  阅读(33)  评论(0编辑  收藏  举报
编辑推荐:
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示