【洛谷P3703】树点涂色
题目
题目链接:https://www.luogu.com.cn/problem/P3703
Bob 有一棵 \(n\) 个点的有根树,其中 \(1\) 号点是根节点。Bob 在每个点上涂了颜色,并且每个点上的颜色不同。
定义一条路径的权值是:这条路径上的点(包括起点和终点)共有多少种不同的颜色。
Bob 可能会进行这几种操作:
1 x
表示把点 \(x\) 到根节点的路径上所有的点染上一种没有用过的新颜色。2 x y
求 \(x\) 到 \(y\) 的路径的权值。3 x
在以 \(x\) 为根的子树中选择一个点,使得这个点到根节点的路径权值最大,求最大权值。
Bob 一共会进行 \(m\) 次操作。
\(1\leq n \leq 10^5\),\(1\leq m \leq 10^5\)。
思路
发现每次修改都是直接把 \(x\) 到根的路径的颜色全部赋值,然后原来的颜色就失效了,容易发现任意时刻每一个节点最多有一个儿子的颜色与其相同。
发现这个操作和 LCT 的 access 十分相似,每一个点只可以保留一条实边。
如果我们把操作一看做将 access(\(x\)),那么一个点的权值就转换为了该点到根节点的虚边的数量。
在 access 的时候,当我们把 \(x\) 与其原来的实儿子的边断开时,它实儿子所在子树的权值全部加 \(1\),连接新的实儿子 \(y\) 后,\(y\) 所在子树内的权值全部减 \(1\)。
那么用一个线段树维护区间和以及区间最大值即可。注意是一个节点所在子树内的权值加减,所以要先 findrt,然后在找到的根节点进行修改。但是 findrt 之后不能 splay,否则会改变 Splay 的形态。
然后对于操作 \(2\),答案即为 \(x,y\) 的权值和减去他们 LCA 的权值的两倍再加上 \(1\)。操作三直接区间最大值即可。
时间复杂度 \(O(m\log^2n)\)。
代码
#include <bits/stdc++.h>
using namespace std;
const int N=100010,LG=18;
int n,m,tot,head[N],dfn[N],rk[N],siz[N],dep[N],f[N][LG+1];
struct edge
{
int next,to;
}e[N*2];
void add(int from,int to)
{
e[++tot]=(edge){head[from],to};
head[from]=tot;
}
struct SegTree
{
int maxn[N*4],lazy[N*4];
void pushdown(int x)
{
if (lazy[x])
{
maxn[x*2]+=lazy[x]; lazy[x*2]+=lazy[x];
maxn[x*2+1]+=lazy[x]; lazy[x*2+1]+=lazy[x];
lazy[x]=0;
}
}
void pushup(int x)
{
maxn[x]=max(maxn[x*2],maxn[x*2+1]);
}
void build(int x,int l,int r)
{
if (l==r)
{
maxn[x]=dep[rk[l]];
return;
}
int mid=(l+r)>>1;
build(x*2,l,mid); build(x*2+1,mid+1,r);
pushup(x);
}
void update(int x,int l,int r,int ql,int qr,int v)
{
if (ql<=l && r<=qr)
{
lazy[x]+=v; maxn[x]+=v;
return;
}
pushdown(x);
int mid=(l+r)>>1;
if (ql<=mid) update(x*2,l,mid,ql,qr,v);
if (qr>mid) update(x*2+1,mid+1,r,ql,qr,v);
pushup(x);
}
int query(int x,int l,int r,int ql,int qr)
{
if (!l || !r) return 0;
if (ql<=l && r<=qr) return maxn[x];
pushdown(x);
int mid=(l+r)>>1,s=0;
if (ql<=mid) s=max(s,query(x*2,l,mid,ql,qr));
if (qr>mid) s=max(s,query(x*2+1,mid+1,r,ql,qr));
return s;
}
}seg;
struct LCT
{
int fa[N],ch[N][2];
bool pos(int x)
{
return x==ch[fa[x]][1];
}
bool notrt(int x)
{
return x==ch[fa[x]][1] || x==ch[fa[x]][0];
}
void rotate(int x)
{
int y=fa[x],z=fa[y],k=pos(x),c=ch[x][k^1];
if (notrt(y)) ch[z][pos(y)]=x; ch[x][k^1]=y; ch[y][k]=c;
if (c) fa[c]=y; fa[y]=x; fa[x]=z;
}
void splay(int x)
{
while (notrt(x))
{
int y=fa[x];
if (notrt(y))
rotate(pos(x)==pos(y)?y:x);
rotate(x);
}
}
int findrt(int x)
{
splay(x);
for (;ch[x][0];x=ch[x][0]) ;
return x;
}
void access(int x)
{
for (int y=0;x;y=x,x=fa[x])
{
splay(x);
int z=ch[x][1]; ch[x][1]=0;
if (z)
{
int p=findrt(z);
seg.update(1,1,n,dfn[p],dfn[p]+siz[p]-1,1);
}
if (y)
{
int p=findrt(y);
seg.update(1,1,n,dfn[p],dfn[p]+siz[p]-1,-1);
}
ch[x][1]=y;
}
}
}lct;
void dfs(int x,int fa)
{
lct.fa[x]=fa;
siz[x]=1; dfn[x]=++tot; rk[tot]=x;
f[x][0]=fa; dep[x]=dep[fa]+1;
for (int i=1;i<=LG;i++)
f[x][i]=f[f[x][i-1]][i-1];
for (int i=head[x];~i;i=e[i].next)
{
int v=e[i].to;
if (v!=fa)
{
dfs(v,x);
siz[x]+=siz[v];
}
}
}
int lca(int x,int y)
{
if (dep[x]<dep[y]) swap(x,y);
for (int i=LG;i>=0;i--)
if (dep[f[x][i]]>=dep[y]) x=f[x][i];
if (x==y) return x;
for (int i=LG;i>=0;i--)
if (f[x][i]!=f[y][i]) x=f[x][i],y=f[y][i];
return f[x][0];
}
int main()
{
memset(head,-1,sizeof(head));
scanf("%d%d",&n,&m);
for (int i=1,x,y;i<n;i++)
{
scanf("%d%d",&x,&y);
add(x,y); add(y,x);
}
tot=0; dfs(1,0);
seg.build(1,1,n);
while (m--)
{
int opt,x,y;
scanf("%d",&opt);
if (opt==1)
{
scanf("%d",&x);
lct.access(x);
}
if (opt==2)
{
scanf("%d%d",&x,&y);
int p=lca(x,y);
int s1=seg.query(1,1,n,dfn[x],dfn[x])+seg.query(1,1,n,dfn[y],dfn[y]);
int s2=2*seg.query(1,1,n,dfn[p],dfn[p]);
printf("%d\n",s1-s2+1);
}
if (opt==3)
{
scanf("%d",&x);
printf("%d\n",seg.query(1,1,n,dfn[x],dfn[x]+siz[x]-1));
}
}
return 0;
}