[SDOI2017] 树点涂色

一、题目

点此看题

三操作是到根的路径哦,不要以为是到子树根的路径。

二、解法

一定要仔细观察题目中的修改有没有什么特殊性质:点 \(x\) 到根节点的路径上所有的点染上一种没有用过的新颜色

我感到了一种神秘的熟悉感,这个东西不是 \(\tt lct\) 的那个 \(\tt access\) 操作吗?也就是我们把虚边看成连边的两个点之间颜色不同,实边看成颜色相同,那么一个点到根的路径权值就是虚边数量\(+1\)

所以我们在 \(\tt access\) 的时候要考虑虚实边变化带来的权值影响,其实每次就是修改一个子树的权值,有 \(dfn\) 序加线段树维护即可。具体的写法中我们要寻找这条边在原树上代表的边对应的点,直接一直往左儿子下边找即可。

\(\tt lct\) 中一个点转到根后,它的祖先要么在拉上去的虚边上面,要么在自己左儿子方向。注意 \(\tt lct\) 上的边并不是原树的结构(虚边指向指向该 \(Splay\) 中中序遍历最靠前的点在原树中的父亲),但是深度关系往往能帮上忙。

这一部分可以看一下魔改之后的 \(\tt access\) 代码:

void access(int x)
{
	for(int y=0;x;x=par[y=x])
	{
		splay(x);//先把x转到根
		if(ch[x][1])//原来的右儿子要变虚了
		{
			int p=find(ch[x][1]);//找到最左的祖先
			upd(1,1,n,dfn[p],dfo[p],1);//在线段树上改
		}
		if(y)//这个点又要变实了
		{
			int p=find(y);
			upd(1,1,n,dfn[p],dfo[p],-1); 
		}
		ch[x][1]=y;
	}
}

但是有人说直接找左儿子好像时间复杂度是错的,但是由于我不会 \(\tt lct\) 的时间复杂度分析所以我也不知道,据说更好的写法是 \(\tt splay\) 里面就维护深度最小的点。

现在看第一个询问操作,其实问的就是 \((x,y)\) 路径之间的虚边数量,直接差分就可以了,设 \(dis[i]\) 等于虚边数量加\(1\),一开始的值就是深度,那么答案是 \(dis[x]+dis[y]-2\cdot dis[lca]+1\),第二个询问操作就是子树内最大值,没什么好说的。

时间复杂度 \(O(n\log^2 n)\)

#include <cstdio>
#include <iostream>
using namespace std;
const int M = 100005;
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
int n,m,tot,f[M],ch[M][2],par[M],fa[M][20];
int Ind,dep[M],dfn[M],dfo[M],mx[4*M],tag[4*M];
struct edge
{
	int v,next;
	edge(int V=0,int N=0) : v(V) , next(N) {}
}e[2*M];
//预处理部分
void dfs(int u,int p)
{
	fa[u][0]=p;
	dfn[u]=++Ind;
	dep[u]=dep[p]+1;
	par[u]=p;//一开始都连虚边 
	for(int i=1;i<20;i++)
		fa[u][i]=fa[fa[u][i-1]][i-1];
	for(int i=f[u];i;i=e[i].next)
	{
		int v=e[i].v;
		if(v==p) continue;
		dfs(v,u);
	}
	dfo[u]=Ind;
}
int lca(int u,int v)
{
	if(dep[u]<dep[v]) swap(u,v);
	for(int i=19;i>=0;i--)
		if(dep[fa[u][i]]>=dep[v])
			u=fa[u][i];
	if(u==v) return u;
	for(int i=19;i>=0;i--)
		if(fa[u][i]^fa[v][i])
			u=fa[u][i],v=fa[v][i];
	return fa[u][0];
}
//线段树部分
void down(int i)
{
	mx[i<<1]+=tag[i];
	tag[i<<1]+=tag[i];
	mx[i<<1|1]+=tag[i];
	tag[i<<1|1]+=tag[i];
	tag[i]=0;
}
void upd(int i,int l,int r,int L,int R,int f)
{
	if(L>r || l>R) return ;
	if(L<=l && r<=R)
	{
		mx[i]+=f;
		tag[i]+=f;
		return ;
	}
	int mid=(l+r)>>1;
	down(i);
	upd(i<<1,l,mid,L,R,f);
	upd(i<<1|1,mid+1,r,L,R,f);
	mx[i]=max(mx[i<<1],mx[i<<1|1]);
}
int ask(int i,int l,int r,int L,int R)
{
	if(L>r || l>R) return 0;
	if(L<=l && r<=R) return mx[i];
	int mid=(l+r)>>1;
	down(i);
	return max(ask(i<<1,l,mid,L,R),ask(i<<1|1,mid+1,r,L,R));
}
//lct部分
int nrt(int x)
{
	return ch[par[x]][0]==x || ch[par[x]][1]==x;
}
int chk(int x)
{
	return ch[par[x]][1]==x;
}
void rotate(int x)
{
	int y=par[x],z=par[y],k=chk(x),w=ch[x][k^1];
	ch[y][k]=w;par[w]=y;
	if(nrt(y)) ch[z][chk(y)]=x;par[x]=z;
	ch[x][k^1]=y;par[y]=x;
}
void splay(int x)
{
	while(nrt(x))
	{
		int y=par[x],z=par[y];
		if(nrt(y))
		{
			if(chk(y)==chk(x)) rotate(y);
			else rotate(x);
		}
		rotate(x);
	}
}
int find(int x)//找到最左儿子 
{
	while(ch[x][0]) x=ch[x][0];
	return x;
}
void access(int x)
{
	for(int y=0;x;x=par[y=x])
	{
		splay(x);
		if(ch[x][1])
		{
			int p=find(ch[x][1]);
			upd(1,1,n,dfn[p],dfo[p],1);
		}
		if(y)
		{
			int p=find(y);
			upd(1,1,n,dfn[p],dfo[p],-1); 
		}
		ch[x][1]=y;
	}
}
signed main()
{
	n=read();m=read();
	for(int i=1;i<n;i++)
	{
		int u=read(),v=read();
		e[++tot]=edge(v,f[u]),f[u]=tot;
		e[++tot]=edge(u,f[v]),f[v]=tot;
	}
	dfs(1,0);
	for(int i=1;i<=n;i++)
		upd(1,1,n,dfn[i],dfn[i],dep[i]);//初始化
	while(m--)
	{
		int op=read(),x=read();
		if(op==1) access(x);
		if(op==2)
		{
			int y=read(),t=lca(x,y);
			printf("%d\n",ask(1,1,n,dfn[x],dfn[x])+
			ask(1,1,n,dfn[y],dfn[y])-2*ask(1,1,n,dfn[t],dfn[t])+1);
		}
		if(op==3)
			printf("%d\n",ask(1,1,n,dfn[x],dfo[x]));
	}
}
posted @ 2021-03-17 15:05  C202044zxy  阅读(113)  评论(0编辑  收藏  举报