• 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
    ",c[i]);
    	}
    }
    

    2020/7/24 update:

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

    2020/7/24 update:

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

  • 相关阅读:
    前端 fetch 通信
    编写高质量的JavaScript代码(一)
    Redis学习笔记1-Redis的介绍和认识
    gitignore不起作用解决的方法
    【我的面经】说说简历的细节——软件开发岗位
    菜鸟的mongoDB学习---(七)MongoDB 备份(mongodump)与恢复(mongorerstore)
    HDU 4927 Series 1
    树状数组求第K小值 (spoj227 Ordering the Soldiers &amp;&amp; hdu2852 KiKi&#39;s K-Number)
    git和SVN的差别
    KVM-Introduce
  • 原文地址:https://www.cnblogs.com/groundwater/p/12391788.html
Copyright © 2020-2023  润新知