• Codeforces 1491H. Yuezheng Ling and Dynamic Tree 题解


    题目链接:H. Yuezheng Ling and Dynamic Tree

    题目大意:给定一棵大小为 (n) 的一 (1) 为根节点的树,树用如下方式给出:输入 (a_2,a_3,dots,a_n),保证 (1leq a_i<i),将 (a_i)(i) 连边形成一棵树。

    接下来有两种操作:

    • 1 l r x(a_i=max(a_i-x,1)(lleq ileq r))
    • 2 u v 查询在当前的 (a) 数组构成的树上 (u,v) 的 LCA。

    两种操作总数一共有 (q) 个。

    (2leq n,qleq 10^5)(2leq lleq rleq n)(1leq xleq 10^5)(1leq u,vleq n)


    题解:将 (a) 序列分块,假设块长为 (B),对于每一个节点,预处理出它的祖先中编号最大的和它不再同一个块的祖先,为了方便,在下文中我们称其为当前点的前驱,我们先考虑如何求两个点的 LCA。

    分两种情况考虑:

    1. (u,v) 不属于同一个块:将属于编号较大的一个块的节点跳至它的前驱。
    2. (u,v) 属于同一个块:接下来还是要分两种情况:
      1. (u) 的前驱和 (v) 的前驱不同:将 (u,v) 同时跳至各自的前驱。
      2. (u) 的前驱和 (v) 的前驱相同:此时可以轮流跳 (u,v) 中编号较大的点的父亲直至两个点相等。

    接下来分析查询的时间复杂度:因为前驱最多不超过 (frac{n}{B}) 个,而暴力跳父亲不超过 (B) 次,所以时间复杂度是 (O(B+frac{n}{B}))

    然后我们再来考虑修改:按照序列分块的惯例,我们分整块和散块来考虑,对于散块,直接暴力修改然后暴力重构前驱即可。

    但是对于整块,我们需要打懒惰标记,但是这种时候无法维护前驱的修改,因为修改一个块的前驱是需要遍历整块的。

    但是我们发现,一个块最多在经过 (B) 次修改之后,块中每一个数的父亲都会在这个块之前,也就是说,我们对于一个块需要暴力重构前驱的次数最多是 (B) 次,之后的可以直接通过懒标记直接减来解决。

    那么分析修改的复杂度,对于散块修改是 (O(B)) 的,对于整块修改(不考虑重构前驱)是 (O(frac{n}{B})) 的,对于重构前驱的总时间复杂度是 (O(nB)) 的。

    综上,若取 (B=sqrt{n}),则可以获得 (O(nsqrt{n}+qsqrt{n})) 的时间复杂度,可以通过本题。

    代码:

    #include <cstdio>
    #include <algorithm>
    const int Maxb=300;
    const int Maxn=100000;
    const int Maxv=(Maxn-1)/Maxb+1;
    int n,q;
    int num[Maxv+5],lazy[Maxv+5];
    int fa[Maxn+5],out[Maxn+5];
    int find_belong(int x){
    	return (x-1)/Maxb+1;
    }
    int find_bel_l(int x){
    	return (x-1)*Maxb+1;
    }
    int find_bel_r(int x){
    	return std::min(n,x*Maxb);
    }
    void build(int x){
    	for(int i=find_bel_l(x);i<=find_bel_r(x);i++){
    		fa[i]=std::max(1,fa[i]-lazy[x]);
    	}
    	lazy[x]=0;
    	for(int i=find_bel_l(x);i<=find_bel_r(x);i++){
    		if(fa[i]<find_bel_l(x)){
    			out[i]=fa[i];
    		}
    		else{
    			out[i]=out[fa[i]];
    		}
    	}
    }
    int find_out(int x){
    	return std::max(1,out[x]-lazy[find_belong(x)]);
    }
    int find_fa(int x){
    	return std::max(1,fa[x]-lazy[find_belong(x)]);
    }
    void update(int l,int r,int x){
    	int bel_l=find_belong(l),bel_r=find_belong(r);
    	if(bel_l==bel_r){
    		for(int i=l;i<=r;i++){
    			fa[i]=std::max(1,fa[i]-x);
    		}
    		build(bel_l);
    		return;
    	}
    	for(int i=l;i<=find_bel_r(bel_l);i++){
    		fa[i]=std::max(1,fa[i]-x);
    	}
    	build(bel_l);
    	for(int i=find_bel_l(bel_r);i<=r;i++){
    		fa[i]=std::max(1,fa[i]-x);
    	}
    	build(bel_r);
    	for(int i=bel_l+1;i<bel_r;i++){
    		num[i]++;
    		lazy[i]=std::min(n,lazy[i]+x);
    		if(num[i]<=Maxb){
    			build(i);
    		}
    	}
    }
    int query(int u,int v){
    	while(u!=v){
    		if(find_belong(u)<find_belong(v)){
    			std::swap(u,v);
    		}
    		if(find_belong(u)>find_belong(v)){
    			u=find_out(u);
    		}
    		else{
    			if(find_out(u)!=find_out(v)){
    				u=find_out(u);
    				v=find_out(v);
    			}
    			else{
    				while(u!=v){
    					if(u<v){
    						std::swap(u,v);
    					}
    					u=find_fa(u);
    				}
    			}
    		}
    	}
    	return u;
    }
    int main(){
    	scanf("%d%d",&n,&q);
    	for(int i=2;i<=n;i++){
    		scanf("%d",&fa[i]);
    	}
    	for(int i=1;i<=find_belong(n);i++){
    		build(i);
    	}
    	for(int i=1;i<=q;i++){
    		int op;
    		scanf("%d",&op);
    		if(op==1){
    			int l,r,x;
    			scanf("%d%d%d",&l,&r,&x);
    			update(l,r,x);
    		}
    		else{
    			int u,v;
    			scanf("%d%d",&u,&v);
    			printf("%d
    ",query(u,v));
    		}
    	}
    	return 0;
    }
    
  • 相关阅读:
    QTreeWidget创建
    Qt QTreeWidget节点的添加+双击响应+删除详解(转)
    Qt QTreeWidget 树形结构实现(转)
    QMessageBox类学习:
    QAction类详解:
    Qt事件和信号的区别 .
    Qt消息机制和事件(二)
    Qt消息机制和事件(一)
    初步开始学习图
    图中最短路径算法(Dijkstra算法)(转)
  • 原文地址:https://www.cnblogs.com/withhope/p/14462136.html
Copyright © 2020-2023  润新知