zoukankan      html  css  js  c++  java
  • [SDOI2017] 树点涂色

    一、题目

    点此看题

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

    二、解法

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

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

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

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

    这一部分可以看一下魔改之后的 ( t 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;
    	}
    }
    

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

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

    时间复杂度 (O(nlog^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
    ",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
    ",ask(1,1,n,dfn[x],dfo[x]));
    	}
    }
    
  • 相关阅读:
    不容易系列之一(错排)
    找新朋友(欧拉函数)
    二分查找
    快速排序(分治)
    归并排序(分治)
    畅通工程(并查集)
    A Knight's Journey (DFS)
    Network Saboteur (DFS)
    Oil Deposits(油田)(DFS)
    Dungeon Master (三维BFS)
  • 原文地址:https://www.cnblogs.com/C202044zxy/p/14549454.html
Copyright © 2011-2022 走看看