• 树链剖分 学习笔记


    树链剖分 MOD

    luogu 3384 模板

    内容主要就是如何在树上用线段树

    问题有两个:

    1. 区间修改某一节点的子树并统计和
    2. 区间修改两节点间的最短路上的节点并统计和

    这里就要引入DFS序的概念

    dfs序

    如上图,可以发现一棵树的前序遍历会使得一个节点的
    子树是一个连续的序列,如果我们建一个数组来记录每个节点所能到达的最深的子节点,我们就能用线段树来维护
    它了。

    这样我们就解决了问题1,但问题2的操作节点编号是不连续的,我们要怎样处理呢?

    如以上述方法标号后的图上两点 (5,8)

    剖分

    我们发现其实图上还是有连续序列的,那么就可以对这些区间分段操作。
    如图,可以发现节点 5 在 1->5 这条链上;
    而 8 可以单独看作一条链,从 8 到 5 的区间就可以用几条链表示出来,那么图中任意两点皆可如此。

    具体操作如下:

    1. 找到其中链头较深的节点,将其指针移至链头的父节点,并把链头至原节点之间的连续区间在线段树上修改。

    2.重复步骤 1 ,直到两节点在一条链上。

    3.对两节点之间的连续区间修改

    但是问题来了,如何让链尽量少(也就是链尽量长,因为明显这样线段操作会更多)。

    我们发现,子树“大”的节点链会更长,那么只需统计一个节点的 子树最大的 子节点,然后先搜这个节点为其编号。

    为此我们要先dfs一遍找子树最大的子节点(顺便把深度,父节点等等预处理掉)

    贴代码:

    void dfs1(int o,int fx){ //当前节点编号和父节点编号
      dep[o]=dep[fx]+1; //深度
      fa[o]=fx;  //父节点
      siz[o]=1;  // siz[]记录子树大小
      hv[o]=o; 
      int ma=0;
      for(int i=head[o];i!=0;i=e[i].next){
      	int x=e[i].to;
      	if(x==fx) continue;
      	dfs1(x,o);
      	siz[o]+=siz[x];
      	if(ma <= siz[x]){ //找出// 子树最大的子节点
      		ma=siz[x];
      		hv[o]=x;
      	}
      }
    }
    

    然后就是编号,这里注意所有数组下标都是原编号而不是我们新给的编号

    void dfs2(int o,int topp){
    	top[o]=topp;  // top[] 存链顶
    	dfn[o]=++cnt; // 编号
        w[cnt]=val[o]; //将原节点的权值转到新编号上
    	if(hv[o]!=o)  // 在叶节点上不能再下搜了 不然会死循环
    	dfs2(hv[o],topp); //先搜最重节点
    	for(int i=head[o];i!=0;i=e[i].next){
    		int x=e[i].to;
    		if(x==fa[o] || x==hv[o]) continue;
    		dfs2(x,x); //新开一个链
    	}
    }
    

    那么为什么这样会快呢?
    不难发现,每一次跳跃最少会减少 1 的深度,
    就算每次都跳,也只有 (lg{n}) 次( (n)是节点数)。而每个链的线段树操作是(lg{n}) 的。
    所以最终的时间复杂度是 $ O(lg^2{n})$.

    剩下就是线段树了。注意树是以新编号建的而数组都是用原编号当的下标。

    最后是代码:

    #include<cstdio>
    #include<algorithm>
    using namespace std;
    #define maxn 100050
    
    
    
    struct node{
    	int next,to;
    };
    node e[maxn<<1];
    int n,m,R,p,cnt;
    int dfn[maxn],fa[maxn],siz[maxn],hv[maxn],dep[maxn],val[maxn],head[maxn],top[maxn],w[maxn];
    int tree[maxn<<2],lazy[maxn<<2];
    
    void add(int from,int to){
    	e[++cnt].next=head[from];
    	e[cnt].to=to;
    	head[from]=cnt;
    }
     
    void dfs1(int o,int fx){
    	dep[o]=dep[fx]+1;
    	fa[o]=fx;
    	siz[o]=1;
    	hv[o]=o;
    	int ma=0;
    	for(int i=head[o];i!=0;i=e[i].next){
    		int x=e[i].to;
    		if(x==fx) continue;
    		dfs1(x,o);
    		siz[o]+=siz[x];
    		if(ma <= siz[x]){
    			ma=siz[x];
    			hv[o]=x;
    		}
    	}
    }
     
    void dfs2(int o,int topp){
    	top[o]=topp;
    	dfn[o]=++cnt;
        w[cnt]=val[o];
    	if(hv[o]!=o) 
    	dfs2(hv[o],topp);
    	for(int i=head[o];i!=0;i=e[i].next){
    		int x=e[i].to;
    		if(x==fa[o] || x==hv[o]) continue;
    		dfs2(x,x);
    	}
    }
     
    void pushup(int o){
    	tree[o]=tree[o<<1]+tree[o<<1|1];
    	tree[o]%=p;
    }
     
    void pushdown(int o,int l,int r){
    	if(lazy[o]==0) return;
    	int mid=(r+l)>>1;
    	lazy[o<<1]+=lazy[o];
    	lazy[o<<1|1]+=lazy[o];
    	tree[o<<1]+=lazy[o]*(mid-l+1);
    	tree[o<<1|1]+=lazy[o]*(r-mid);
    	lazy[o<<1]%=p;
    	lazy[o<<1|1]%=p;
    	tree[o<<1]%=p;
    	tree[o<<1|1]%=p;
    	lazy[o]=0;
    } 
    
    void plus(int o,int l,int r,int ql,int qr,int v){
    	if(ql<=l && r<=qr){
    		tree[o]+=v*(r-l+1);
    		tree[o]%=p;
    		lazy[o]+=v;
    		lazy[o]%=p;
    		return; 
    	}
    	pushdown(o,l,r);
    	int mid=(r+l)>>1;
    	if(ql<=mid) plus(o<<1,l,mid,ql,min(qr,mid),v);
    	if(qr>mid) plus(o<<1|1,mid+1,r,max(mid+1,ql),qr,v);
    	pushup(o);
    }
     
     
    void build(int o,int l,int r){
    	if(r<=l){
    		tree[o]=w[l];
    		return;
    	}
    	int mid=(r+l)>>1;
    	build(o<<1,l,mid);
    	build(o<<1|1,mid+1,r);
    	pushup(o);
    }
     
    int query(int o,int l,int r,int ql,int qr){
    	if(ql<=l && r<=qr) return tree[o];
    	pushdown(o,l,r);
    	int mid=(r+l)>>1,ret=0;
    	if(ql<=mid) ret+=query(o<<1,l,mid,ql,min(qr,mid)),ret%=p;
    	if(qr>mid) ret+=query(o<<1|1,mid+1,r,max(ql,mid+1),qr),ret%=p;
    	return ret;
    }
    
    void plus1(int x,int y,int v){
    	while(top[x]!=top[y]){
    		if(dep[top[x]]<dep[top[y]]) swap(x,y);
    		plus(1,1,n,dfn[top[x]],dfn[x],v);
    		x=fa[top[x]];
    	}
    	if(dep[x]>dep[y]) swap(x,y);
    	plus(1,1,n,dfn[x],dfn[y],v);
    }
    
    void plus2(int x,int v){
    	plus(1,1,n,dfn[x],dfn[x]+siz[x]-1,v);
    }
     
    int query1(int x,int y){
    	int ret=0;
    	while(top[x]!=top[y]){
    		if(dep[top[x]]<dep[top[y]]) swap(x,y);
    		ret+=query(1,1,n,dfn[top[x]],dfn[x]),ret%=p;
    		x=fa[top[x]];
    	}
    	if(dep[x]>dep[y]) swap(x,y);
    	ret+=query(1,1,n,dfn[x],dfn[y]);
    	return ret%=p;
    }
    
    int query2(int x){
    	return query(1,1,n,dfn[x],dfn[x]+siz[x]-1)%p;
    }
      
    void work(){
    	dfs1(R,0);
    	dfs2(R,R);
    	build(1,1,n);
    	for(int i=1;i<=m;i++){
    		int opt,x,y,z;
    		scanf("%d %d",&opt,&x);
    		if(opt==1){
    			scanf("%d %d",&y,&z);
    			plus1(x,y,z);
    		}
    		if(opt==2){
    			scanf("%d",&y);
    			printf("%d
    ",query1(x,y));
    		}
    		if(opt==3){
    			scanf("%d",&y);
    			plus2(x,y);
    		}
    		if(opt==4){
    			printf("%d
    ",query2(x));
    		}
    	}
    }
     
    void input(){
    	scanf("%d %d %d %d",&n,&m,&R,&p);
    	for(int i=1;i<=n;i++){
    		scanf("%d",&val[i]);
    	}
    	for(int i=1;i<n;i++){
    		int tp1,tp2;
    		scanf("%d %d",&tp1,&tp2);
    		add(tp1,tp2);
    		add(tp2,tp1);
    	}
    	cnt=0;
    }
     
    int main(){
    	input();
    	work();
    	return 0;
    }
    

    内容采用“知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议”进行许可。请您在转载时注明来源及链接。


    · (frak by;;thorn\_)

  • 相关阅读:
    阿里云安骑士和云盾不能检测连接服务器问题
    UDP反射DDoS攻击原理和防范
    linux查看端口是否开放
    记一次阿里云服务器被用作DDOS攻击肉鸡
    记一次阿里云负载测试
    mysql定时任务event——清理过期数据
    ansible(一)
    基于python实现的三方组件----Celery
    MongoDB
    flask相关使用
  • 原文地址:https://www.cnblogs.com/thornblog/p/11718381.html
Copyright © 2020-2023  润新知