【题目描述】
Bob 有一棵 \(n\) 个点的有根树,其中 \(1\) 号点是根节点。Bob 在每个点上涂了颜色,并且每个点上的颜色不同。
定义一条路径的权值是:这条路径上的点(包括起点和终点)共有多少种不同的颜色。
Bob可能会进行这几种操作:
-
1 x
表示把点 \(x\) 到根节点的路径上所有的点染上一种没有用过的新颜色。 -
2 x y
求 \(x\) 到 \(y\) 的路径的权值。 -
3 x
在以 \(x\) 为根的子树中选择一个点,使得这个点到根节点的路径权值最大,求最大权值。
Bob一共会进行 \(m\) 次操作
【输入格式】
第一行两个数 \(n,m\)。
接下来 \(n-1\) 行,每行两个数 \(a,b\),表示 \(a\) 与 \(b\) 之间有一条边。
接下来 \(m\) 行,表示操作,格式见题目描述
【输出格式】
每当出现 \(2,3\) 操作,输出一行。
如果是 \(2\) 操作,输出一个数表示路径的权值
如果是 \(3\) 操作,输出一个数表示权值的最大值
题解
想了5分钟就决定去看题解的我这种蒟蒻是屑
看了题解就开始写 居然一次就过了 学数据结构学傻了(确信)
我们先来看一下这个1操作 由于每次都是把到根的路径染一种全新的颜色 所以任意一种颜色的所有点都一定在连续的一条路径上面
然后再看这个 每次都是修改从点\(x\)到根的路径 有没有想到LCT的access操作 access就是将根到点\(x\)的路径上的点全部扔到一个splay里面
所以思路就很显然了 我们用LCT来维护点的颜色 也就是把所有颜色相同的点全部都放在一棵splay里面
初始时因为所有点颜色各不相同 所以所有边都是虚边 也就是说每个点都是一棵splay
然后每次1操作直接access即可
再看看两种询问如何回答
先看看2操作 这里有一个性质 记\(x\)到根路径上的颜色种数为f[x]
,那么\(x,y\)路径上的颜色种数为f[x]+f[y]-2*f[lca]+1
一眼看上去很正常 等等 +1
是什么鬼
\(lca\)那个点的颜色假设是\(c\) 那么\(c\)这种颜色在f[x]
和f[y]
被各加了一次 又在-2*f[lca]
被减了两次 所以还要加回来
问题是f[x]
在access时要怎么维护
从oi-wiki盗了张图 _(:з」∠)_
有一棵长成这样的树
假设现在它的轻重边划分是这样的
(注意 这棵是原树 不是LCT)
现在我们把\(A\rightarrow N\)的点全部染成一种新的颜色
边的虚实要这样修改
那么\(N\rightarrow O\)这条边就会从实边变成虚边 因为\(N\)的颜色不再和\(O\)一样了
所以我们把\(O\)子树中所有点的f[x]+1
再往上走的时候\(I\rightarrow K\)也从实边变成虚边 一样把\(K\)子树中所有点f[x]+1
然后我们发现\(I\rightarrow L\)这条边由虚变实了 也就是说原来\(I\)和\(L\)的颜色不一样 现在一样了 所以把\(L\)子树中所有点f[x]-1
总而言之 就是虚变实要-1
实变虚要+1
在access里面实现的时候就是每次找到当前这棵splay里面最靠左的点(按定义这个点是原树中深度最浅的点) 把这个点的子树+1
或-1
不知道如何解释。。。具体见代码
然后就好办了 子树区间加用线段树就完了
2操作就是找\(lca\)然后线段树单点查询 直接输出即可
3操作就是线段树区间取\(\text{max}\)
初始时由于每个点的颜色互不相同 所以f[x]
就等于\(x\)在原树上的深度(也就是到根的路径上有多少个点)
码量极大 我写了200行。。。(虽然一遍就过了)
看网上有人说这题是LCT+树剖 但是实际上没有用到树剖的思想。。。顶多用了一下求LCA
但是似乎也有纯树剖切此题的神犇Orz
代码
#include <bits/stdc++.h>
#define N 100005
using namespace std;
inline int read() {
int x = 0, f = 1; char ch = getchar();
for (; ch > '9' || ch < '0'; ch = getchar()) if (ch == '-') f = -1;
for (; ch <= '9' && ch >= '0'; ch = getchar()) x = (x << 3) + (x << 1) + (ch ^ '0');
return x * f;
}
int n, m, head[N], pre[N<<1], to[N<<1], sz;
inline void addedge(int u, int v) {
pre[++sz] = head[u]; head[u] = sz; to[sz] = v;
pre[++sz] = head[v]; head[v] = sz; to[sz] = u;
}
int fa[N], dfn[N], ed[N], rnk[N], tme, d[N], top[N], son[N], siz[N];
//由于还要维护什么深度 dfs序之类的东西 所以直接来一次树剖比较方便
void dfs(int x) {
siz[x] = 1;
for (int i = head[x]; i; i = pre[i]) {
int y = to[i];
if (y == fa[x]) continue;
fa[y] = x;
d[y] = d[x] + 1;
dfs(y);
siz[x] += siz[y];
if (!son[x] || siz[son[x]] < siz[y]) son[x] = y;
}
}
void dfs2(int x, int tp) {
top[x] = tp;
dfn[x] = ++tme; rnk[tme] = x;
if (son[x]) dfs2(son[x], tp);
for (int i = head[x]; i; i = pre[i]) {
int y = to[i];
if (y == fa[x] || y == son[x]) continue;
dfs2(y, y);
}
ed[x] = tme;
}
inline int LCA(int x, int y) {
while (top[x] != top[y]) {
if (d[top[x]] < d[top[y]]) swap(x, y);
x = fa[top[x]];
}
if (d[x] > d[y]) swap(x, y);
return x;
}
namespace Segtree{
struct tree{
int l, r, mx, tag;
} tr[N<<2];
#define lson ind<<1
#define rson ind<<1|1
void build(int ind, int l, int r) {
tr[ind].l = l; tr[ind].r = r; tr[ind].tag = 0;
if (l == r) {
tr[ind].mx = d[rnk[l]];
return;
}
int mid = (l + r) >> 1;
build(lson, l, mid); build(rson, mid+1, r);
tr[ind].mx = max(tr[lson].mx, tr[rson].mx);
}
inline void pushdown(int ind) {
if (!tr[ind].tag) return;
int v = tr[ind].tag; tr[ind].tag = 0;
tr[lson].mx += v; tr[lson].tag += v;
tr[rson].mx += v; tr[rson].tag += v;
}
void update(int ind, int x, int y, int v) {
int l = tr[ind].l, r = tr[ind].r;
if (x <= l && r <= y) {
tr[ind].mx += v;
tr[ind].tag += v;
return;
}
pushdown(ind);
int mid = (l + r) >> 1;
if (x <= mid) update(lson, x, y, v);
if (mid < y) update(rson, x, y, v);
tr[ind].mx = max(tr[lson].mx, tr[rson].mx);
}
int query(int ind, int x, int y) {
int l = tr[ind].l, r = tr[ind].r;
if (x <= l && r <= y) return tr[ind].mx;
pushdown(ind);
int mid = (l + r) >> 1, ret = 0;
if (x <= mid) ret = max(ret, query(lson, x, y));
if (mid < y) ret = max(ret, query(rson, x, y));
return ret;
}
#undef lson
#undef rson
}
namespace LCT{
int ch[N][2], fa[N], tag[N];
#define lson ch[x][0]
#define rson ch[x][1]
//并不需要 pushup
inline void rev(int x) {
swap(lson, rson);
tag[x] ^= 1;
}
inline void pushdown(int x) {
if (!tag[x]) return;
if (lson) rev(lson);
if (rson) rev(rson);
tag[x] = 0;
}
inline bool isroot(int x) {
return ch[fa[x]][0] != x && ch[fa[x]][1] != x;
}
inline void rotate(int x) {
int y = fa[x], z = fa[y], k = (ch[y][1]==x);
if (!isroot(y)) ch[z][ch[z][1]==y] = x;
fa[x] = z;
ch[y][k] = ch[x][k^1]; fa[ch[x][k^1]] = y;
ch[x][k^1] = y; fa[y] = x;
}
int q[N], top;
inline void splay(int x) {
q[top=1] = x;
for (int i = x; !isroot(i); i = fa[i]) q[++top] = i;
while (!top) pushdown(q[top--]);
while (!isroot(x)) {
int y = fa[x], z = fa[y];
if (!isroot(y)) ((ch[z][1]==y) ^ (ch[y][1]==x)) ? rotate(x) : rotate(y);
rotate(x);
}
}
inline int pre(int x) { //找到splay中权值(也就是原树中的深度)最小的点
while (lson) x = lson;
return x;
}
inline void access(int x) {
for (int i = 0; x; i = x, x = fa[x]) {
splay(x);
if (ch[x][1]) {
int y = pre(ch[x][1]);
//原树中 fa[y]->y 这条边由实变虚
Segtree::update(1, dfn[y], ed[y], 1);
}
ch[x][1] = i;
if (ch[x][1]) {
int y = pre(ch[x][1]);
//原树中 fa[y]->y 这条边由虚变实
Segtree::update(1, dfn[y], ed[y], -1);
}
}
}
#undef lson
#undef rson
}
int main() {
n = read(), m = read();
for (int i = 1, u, v; i < n; i++) {
u = read(), v = read();
addedge(u, v);
}
d[1] = 1;
dfs(1); dfs2(1, 1);
Segtree::build(1, 1, n);
for (int i = 1; i <= n; i++) {
LCT::ch[i][0] = LCT::ch[i][1] = LCT::tag[i] = 0;
LCT::fa[i] = fa[i];
}
for (int i = 1, tp, x, y; i <= m; i++) {
tp = read();
if (tp == 1) {
x = read();
LCT::access(x);
} else if (tp == 2) {
x = read(), y = read();
int lca = LCA(x, y);
printf("%d\n", Segtree::query(1, dfn[x], dfn[x]) + Segtree::query(1, dfn[y], dfn[y]) - 2 * Segtree::query(1, dfn[lca], dfn[lca]) + 1);
//namespace 的名字一定记得取短点 QAQ
} else {
x = read();
printf("%d\n", Segtree::query(1, dfn[x], ed[x]));
}
}
return 0;
}