GMOJ 3883. 【NOIP2014模拟】线段树(segment) 题解

Warning:本做法非一般做法。

首先

  1. 把边权下放成点权(即每个点的颜色表示其到父亲结点的边的颜色),以下内容都是按点权写的。

  2. 询问的两点称AB,修改时的当前点称X A与B的LCA称LCA

  3. \(UP\)数组为当前点上面有可能能修改的最深的节点(默认为该点的父亲)可以理解为树链剖分的top

    一堆废话

简略版

程序分五步

  1. 离线倒序处理。

  2. 然后,求每对询问点的LCA。

  3. 然后,把AB的最短路径拆成A到LCA和B到LCA的两条路径。

  4. 然后,暴力跳边修改(不修改LCA)。

  5. 输出答案。

结束了,完结撒花*\( ̄▽ ̄)/*。

非常好我啥也没讲(逃)。

详细版

  1. 好理解,不解释。
  2. 使用Tarjan算法求LCA。什么?你不知道求LCA的Tarjan算法?戳我学习
  3. 不解释。
  4. 你看,两条路径的重叠部分的顶端一定是这两条路径的LCA的深的那个,对不对 不对,没证明没真相 。所以我们把\(UP_X\)设为当前这条路径的顶端(LCA)。因为LCA我们不能修改,所以上面有可能能修改的最深的节点(\(UP\)的定义)为LCA。
  5. ……

FAQ

  1. 我的Tarjan超时了:不可能,那是你的问题
  2. 如果\(UP\)设为LCA后LCA也被修改了呢?:那是修改LCA那条路径要干的事,跟我有什么关系,跳了修改不了就再跳。
  3. 怎么跳边?:一直将当前点设为\(UP_x\)
  4. 我的Tarjan爆栈了:这正是我接下来要讲的:

人工栈

一般的Tarjan这道题是只能拿80分的,因为这题卡栈。

代码大致模板:

while(栈未空){
    if(第一次进栈){  //遍历变量为0,或再弄个数组,如树剖的第二次DFS因为要先遍历重儿子所以要进栈两遍
        处理信息     //结点进入时可以自己处理的信息
        设置遍历变量
    }else{
        处理信息     //结点退出后需要其父亲处理的信息
    	更改遍历变量
    }
    for(;不能遍历该结点&&遍历变量不为0;更改遍历变量)
    if(可以遍历该结点){
        栈顶=该结点
       	处理信息  //结点进入时需要其父亲处理的信息
        栈大小++
    }else{
      	处理信息  //结点退出后可以自己处理的信息
        栈大小--
    }
}

Tarjan核心代码:

//size为栈的大小
//st为栈
//i[x]为栈的第x项的遍历变量
//uni为Tarjan的并查集的合并
//root为并查集的……你懂的
//dp为深度
//b[x][0]下一条边 b[x][1]入点
//a为边的头
//fh为询问的头
//f[x][0]下一个询问 f[x][1]询问的另一点 f[x][2]询问的编号
//lca顾名思义
while(size){
    if(!i[size]){
        i[size]=a[st[size]];   //设置遍历变量
    }else{
        uni(b[i[size]][1],st[size]);  //结点退出后需要其父亲处理的信息
        i[size]=b[i[size]][0];  //更改遍历变量
    }
    if(i[size]){
        dp[b[i[size]][1]]=dp[st[size]]+1; //结点进入时需要其父亲处理的信息
        st[size+1]=b[i[size]][1];  //设置栈顶
        size++;  //增加栈的大小
    }else{
        for(int i=fh[st[size]];i;i=f[i][0]){   //Tarjan核心代码
            int get=root(f[i][1]);
            if((get!=f[i][1])||(get==st[size])){
                lca[f[i][2]]=get;
            }
        }
        size--;  //退栈
    }
}

真·完结撒花*\( ̄▽ ̄)/*

完整代码

压行编译开关:

1. 滥用for循环,=
2. 允许?运算符
3. 相似数据赋初值压行
#include<cstdio>
#define rg register
#define N 500010
using namespace std;
int n,m,s[N],a[N],fh[N],b[N][2],f[N<<1][3],q[N][3],lca[N],c[N],up[N],dp[N],st[N],i[N];  //数组太多,解释不过来QAQ
int read(){
	rg char c=getchar();
	for(;c<33;c=getchar());
	rg int f=c-48;
	for(c=getchar();(c>47)&&(c<58);c=getchar()){
		f=(f<<3)+(f<<1)+c-48;
	}
	return(f);
}
int add(int l,int x,int y){  //l编号 “从x到y的一条边”
	b[l][0]=a[x];
	b[l][1]=y;
	a[x]=l;
}
int fw(int l,int x,int y,int z){  //l编号 “第z次从x到y的LCA的一次查询”
	f[l][0]=fh[x];
	f[l][1]=y;
	f[l][2]=z;
	fh[x]=l;
}
int root(int m){
	return(s[m]?s[m]=root(s[m]):m);
}
void uni(int x,int y){
	s[x]=y;
}
void tarjan(){
	int size=1;//此处上方已有注释
	st[1]=1;
	while(size){
		if(!i[size]){
			i[size]=a[st[size]];
		}else{
			uni(b[i[size]][1],st[size]);
			i[size]=b[i[size]][0];
		}
		if(i[size]){
			dp[b[i[size]][1]]=dp[st[size]]+1;
			st[size+1]=b[i[size]][1];
			size++;
		}else{
			for(rg int i=fh[st[size]];i;i=f[i][0]){
				int get=root(f[i][1]);
				if((get!=f[i][1])||(get==st[size])){
					lca[f[i][2]]=get;
				}
			}
			size--;		
		}
	}
}
void sg(int x,int l,int cl){ //x上面有说 l为LCA cl为修改的颜色
	for(int now;dp[x]>dp[l];x=now){ //一定要大于,不能修改LCA
		now=up[x];
                up[x]=l;                //更正确的写法
		if(!c[x]){
			c[x]=cl;
//			up[x]=l;        原写法
		}
	}
}
int main(){
	n=read();m=read();
	for(rg int i=2;i<=n;i++){
		add(i-1,up[i]=read(),i);
	}
	for(rg int i=1;i<=m;i++){
		q[i][0]=read();q[i][1]=read();q[i][2]=read();
		fw((i<<1)-1,q[i][0],q[i][1],i);
		fw(i<<1,q[i][1],q[i][0],i);
	}
	tarjan();
	for(rg int i=m;i;i--){
		sg(q[i][0],lca[i],q[i][2]);
		sg(q[i][1],lca[i],q[i][2]);
	}
	for(rg int i=2;i<=n;i++){
		printf("%d\n",c[i]);
	}
}

2020/7/24 update:

这个东西时间复杂度其实有问题,可以被卡成 \(O(n^2)\) ,但这题数据似乎没有为这种奇怪的水法做准备。

2020/7/24 update:

没错是同一天,就差几分钟
在更改up数组时,对每一个经过的点都更改up似乎就不会被卡了。程序已换。
修改了一些因为信息不对等导致的问题

posted @ 2020-03-01 20:21  ToUNVRSe  阅读(138)  评论(0)    收藏  举报