【luogu P3703】树点涂色
树点涂色
题目链接:luogu P3703
题目大意
给你一个树,每个点有颜色,初始互不相同,根节点是 1,要你维护几个操作。
给一个从根出发的路径上点染上一个新的颜色。
询问一个路径中的点有多少种颜色。
找在一个子树中找一个点使得它到根节点的路径上的点的颜色种数最多。
思路
首先我们看到染色一定是从根节点出发的一条路径。
那我们会联想到 LCT 的 access 操作。
那我们可以想到将 LCT 的虚边看做是连着两个颜色不同的边,实边是连着颜色相同的。
那接着要怎么求权值呢?
我们考虑先看看第三问怎么搞,那看到子树最大,我们考虑用一个线段树,把原本的树按 dfs 序排。
然后我们维护从起点到某个点要经过的颜色数,然后放在线段树上就是区间内点这个值的最小值。
这样我们会发现一个子树会在连续的 dfs 序上,就可以直接线段树查询了。
那接着求两个点之间,那容易想到直接单点查询它们两个的值和它们的 LCA 的值,然后 \(f_x+f_y-2\times f_{LCA}+1\)。
但是接着是容易想到染色操作就不是单纯的普通 access 操作了。
那你想想你一染色会发生什么。
你会不停地找一段又一段连续的颜色,然后打通它们,把不同颜色的边变成虚的。
那变成虚的那部分就是要颜色数加一,那你变成实的就是要颜色数减一,线段树搞搞就好。
然后就可以了。
代码
#include<cstdio>
#include<algorithm>
using namespace std;
struct node {
int to, nxt;
}e[200001];
int n, m, l[100001], r[100001], le[100001], KK;
int tmpp, op, x, y, fa[100001];
int v[400001], lz[400001], fath[100001][21];
int deg[100001], st[100001], ed[100001];
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 up_(int now) {
v[now] = max(v[now << 1], v[now << 1 | 1]);
}
void down_(int now) {
if (!lz[now]) return ;
v[now << 1] += lz[now];
v[now << 1 | 1] += lz[now];
lz[now << 1] += lz[now];
lz[now << 1 | 1] += lz[now];
lz[now] = 0;
}
void add(int now, int l, int r, int L, int R, int val) {
if (L <= l && r <= R) {
v[now] += val;
lz[now] += val;
return ;
}
down_(now);
int mid = (l + r) >> 1;
if (L <= mid) add(now << 1, l, mid, L, R, val);
if (mid < R) add(now << 1 | 1, mid + 1, r, L, R, val);
up_(now);
}
int query(int now, int l, int r, int L, int R) {
if (L <= l && r <= R) return v[now];
down_(now);
int mid = (l + r) >> 1, re = 0;
if (L <= mid) re = query(now << 1, l, mid, L, R);
if (mid < R) re = max(re, query(now << 1 | 1, mid + 1, r, L, R));
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 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;
}
void Splay(int x) {
while (nrt(x)) {
if (nrt(fa[x])) {
if (ls(x) == ls(fa[x])) rotate(fa[x]);
else rotate(x);
}
rotate(x);
}
}
int get_root(int x) {
while (l[x]) x = l[x];
return x;
}
void access(int x) {
int lst = 0;
for (; x; x = fa[x]) {
Splay(x);
int tmp = r[x];
if (tmp) {//把不是在链那边的别的分支都加一个颜色
tmp = get_root(tmp);
add(1, 1, n, st[tmp], ed[tmp], 1);
}
if (lst) {//把自己的链都减(每次找到的是同一个颜色的,所以减一就行)
tmp = get_root(lst);
add(1, 1, n, st[tmp], ed[tmp], -1);
}
r[x] = lst;
lst = x;
}
}
int LCA(int x, int y) {
if (deg[x] < deg[y]) swap(x, y);
for (int i = 20; i >= 0; i--)
if (deg[fath[x][i]] >= deg[y])
x = fath[x][i];
if (x == y) return x;
for (int i = 20; i >= 0; i--)
if (fath[x][i] != fath[y][i]) {
x = fath[x][i];
y = fath[y][i];
}
return fath[x][0];
}
void dfs(int now, int father) {//初始的 dfs
fath[now][0] = father;
fa[now] = father;
st[now] = ++tmpp;
deg[now] = deg[father] + 1;
add(1, 1, n, st[now], st[now], deg[now]);
for (int i = le[now]; i; i = e[i].nxt)
if (e[i].to != father) dfs(e[i].to, now);
ed[now] = tmpp;
}
int main() {
scanf("%d %d", &n, &m);
for (int i = 1; i < n; i++) {
scanf("%d %d", &x, &y);
add(x, y);
}
dfs(1, 0);
for (int i = 1; i <= 20; i++)
for (int j = 1; j <= n; j++)
fath[j][i] = fath[fath[j][i - 1]][i - 1];
while (m--) {
scanf("%d", &op);
if (op == 1) {
scanf("%d", &x);
access(x);
continue;
}
if (op == 2) {
scanf("%d %d", &x, &y);
int lca = LCA(x, y);
printf("%d\n", query(1, 1, n, st[x], st[x]) + query(1, 1, n, st[y], st[y]) - 2 * query(1, 1, n, st[lca], st[lca]) + 1);
continue;
}
if (op == 3) {
scanf("%d", &x);
printf("%d\n", query(1, 1, n, st[x], ed[x]));
continue;
}
}
return 0;
}