zoukankan      html  css  js  c++  java
  • 浅谈算法——树链剖分

    前言

    首先树链剖分需要使用到线段树知识,不会线段树的童鞋请移步至浅谈算法——线段树

    在做题中我们会看到一些“在一棵树上进行路径修改、求极值、求和”的题,乍一看能够用线段树解决,其实仅仅凭线段树是根本无法完成的。这时候,我们就需要用到一种看起来高级的复杂算法——树链剖分

    基本概念

    重儿子:size[u](表示以u为根节点的子树节点个数)为v的子节点中最大的,称u为v的重儿子
    轻儿子:v的其他子节点
    重边:v与其重儿子的连边
    轻边:v与其轻儿子的连边
    重链:重边组成的一条链

    如图所示,圆圈内为size,粗边为重边,我们称某条路径为重链,当且仅当它全部由重边组成。不存在轻链这一说法

    任意一个点到根的路径上,不超过(log n)条轻边,也不超过(log n)条重链,这是整个算法时间复杂度的保证,证明略。因为我也不会证明

    基本操作

    树链:树上的路径
    剖分:把路径分为两类

    树链剖分的操作为两次dfs

    • 找重边
    • 把重边连成重链

    记size[v]表示以v为根的儿子节点的个数,deep[v]表示v的深度(根深度为1),top[v]表示v所在重链的顶端节点,fa[v]表示v的父亲节点,Rem[v]表示v的重儿子,ID[v]表示v的新编号

    第一遍dfs将size,deep,fa,Rem求出来

    第二遍dfs的时候,每次优先dfs重儿子,这样可以使得重儿子的编号连续

    如下图示,粗边为重边,细边为轻边。节点边带红点的说明其为该条重链的顶端,蓝色数字代表其新编号

    为什么要按这种方式去进行树链剖分?
    因为在剖分完之后,我们可以发现重链上的点的编号是连续的,所以我们可以用线段树或其他高级数据结构去维护重链上的信息,由于每条重链所占据的编号互不相同,所以我们可以把这些重链首尾相连,放到一个高级数据结构上进行维护。同时我们认为一个点也属于一条重链。轻边的信息可以直接维护。因为它不好维护,除非把边权下移作为点权

    树上基本操作

    单点修改:直接按新编号对应单点修改即可
    路径修改
    1、u和v在同一条重链上,直接线段树区间修改即可
    2、u和v不在同一条重链上,我们要想办法把它们往同一条重链上靠

    • fa[top[u]]与v在同一条重链上,两次做区间修改即可
    • u经过若干条重链与轻边后和v在同一条重链上,多次做区间修改即可
    • u和v都经过若干条重链与轻边后在同一条重链上,那么每次找到深度较深的点x,做一次区间修改后,在将该点跳到fa[top[x]],知道u和v在同一条重链上

    前两种情况是特殊的第三种情况。
    查询操作:和路径修改类似,求极值,最大值最小值等,在线段树操作时按照具体情况修改即可

    由于经过的重链个数不会超过(log n)级别,因此整个树链剖分的复杂度为(O(nlog^2n)),但实际上比某些(O(nlog n))的算法要快

    例题

    P3384 【模板】树链剖分

    Description
    如题,已知一棵包含N个结点的树(连通且无环),每个节点上包含一个数值,需要支持以下操作:
    操作1: 格式: 1 x y z 表示将树从x到y结点最短路径上所有节点的值都加上z
    操作2: 格式: 2 x y 表示求树从x到y结点最短路径上所有节点的值之和
    操作3: 格式: 3 x z 表示将以x为根节点的子树内所有节点值都加上z
    操作4: 格式: 4 x 表示求以x为根节点的子树内所有节点值之和

    Input
    第一行包含4个正整数N、M、R、P,分别表示树的结点个数、操作个数、根节点序号和取模数(即所有的输出结果均对此取模)。
    接下来一行包含N个非负整数,分别依次表示各个节点上初始的数值。
    接下来N-1行每行包含两个整数x、y,表示点x和点y之间连有一条边(保证无环且连通)
    接下来M行每行包含若干个正整数,每行表示一个操作,格式如下:
    操作1: 1 x y z
    操作2: 2 x y
    操作3: 3 x z
    操作4: 4 x

    Output
    输出包含若干行,分别依次表示每个操作2或操作4所得的结果(对P取模

    Sample Input
    5 5 2 24
    7 3 7 8 0
    1 2
    1 5
    3 1
    4 1
    3 4 2
    3 2 2
    4 5
    1 5 1 3
    2 1 3

    Sample Output
    2
    21

    HNIT
    对于100%的数据:$ N leq {10}^5, M leq {10}^5$

    故输出应依次为2、21(重要的事情说三遍:记得取模)

    /*program from Wolfycz*/
    #include<cmath>
    #include<cstdio>
    #include<cstring>
    #include<iostream>
    #include<algorithm>
    #define inf 0x7f7f7f7f
    using namespace std;
    typedef long long ll;
    typedef unsigned int ui;
    typedef unsigned long long ull;
    inline int read(){
    	int x=0,f=1;char ch=getchar();
    	for (;ch<'0'||ch>'9';ch=getchar())	if (ch=='-')    f=-1;
    	for (;ch>='0'&&ch<='9';ch=getchar())	x=(x<<1)+(x<<3)+ch-'0';
    	return x*f;
    }
    inline void print(int x){
    	if (x>=10)     print(x/10);
    	putchar(x%10+'0');
    }
    const int N=1e5;
    int v[N+10],dfn[N+10],Line[N+10];
    int n,m,root,mod;
    struct Segment{//线段树Lazy标记操作
    	#define ls (p<<1)
    	#define rs ((p<<1)|1)
    	int tree[(N<<2)+10],Lazy[(N<<2)+10];
    	void updata(int p){tree[p]=(tree[ls]+tree[rs])%mod;}
    	void add_tag(int p,int l,int r,int v){
    		tree[p]=(tree[p]+1ll*(r-l+1)*v)%mod;
    		Lazy[p]=(Lazy[p]+v)%mod;
    	}
    	void pushdown(int p,int l,int r){
    		if (!Lazy[p])	return;
    		int mid=(l+r)>>1;
    		add_tag(ls,l,mid,Lazy[p]),add_tag(rs,mid+1,r,Lazy[p]);
    		Lazy[p]=0;
    	}
    	void build(int p,int l,int r){
    		if (l==r){tree[p]=v[Line[l]]%mod;return;}
    		int mid=(l+r)>>1;
    		build(ls,l,mid),build(rs,mid+1,r);
    		updata(p);
    	}
    	void insert(int p,int l,int r,int x,int y,int v){
    		if (x<=l&&r<=y){add_tag(p,l,r,v);return;}
    		int mid=(l+r)>>1;
    		pushdown(p,l,r);
    		if (x<=mid)	insert(ls,l,mid,x,y,v);
    		if (y>mid)	insert(rs,mid+1,r,x,y,v);
    		updata(p);
    	}
    	int query(int p,int l,int r,int x,int y){
    		if (x<=l&&r<=y)	return tree[p];
    		pushdown(p,l,r);
    		int mid=(l+r)>>1,res=0;
    		if (x<=mid)	res=(res+query(ls,l,mid,x,y))%mod;
    		if (y>mid)	res=(res+query(rs,mid+1,r,x,y))%mod;
    		return res;
    	}
    }Tree;
    struct Start{
    	int pre[(N<<1)+10],child[(N<<1)+10],now[N+10];
    	int deep[N+10],size[N+10],fa[N+10],Rem[N+10],top[N+10];
    	int tot,cnt;
    	void join(int x,int y){pre[++tot]=now[x],now[x]=tot,child[tot]=y;}
    	void build(int x,int Deep){//第一遍dfs
    		deep[x]=Deep,size[x]=1;
    		for (int p=now[x],son=child[p],Max=0;p;p=pre[p],son=child[p]){
    			if (son==fa[x])	continue;
    			fa[son]=x;
    			build(son,Deep+1);
    			size[x]+=size[son];
    			if (Max<size[son])	Max=size[son],Rem[x]=son;
    		}
    	}
    	void dfs(int x){//第二遍dfs
    		if (!x)	return;
    		Rem[fa[x]]==x?top[x]=top[fa[x]]:top[x]=x;
    		dfn[x]=++cnt,Line[cnt]=x;
    		dfs(Rem[x]);
    		for (int p=now[x],son=child[p];p;p=pre[p],son=child[p]){
    			if (son==fa[x]||son==Rem[x])	continue;
    			dfs(son);
    		}
    	}
    	void insert(int x,int y,int z){//区间操作
    		while (top[x]!=top[y]){
    			if (deep[top[x]]<deep[top[y]])	swap(x,y);
    			Tree.insert(1,1,n,dfn[top[x]],dfn[x],z);
    			x=fa[top[x]];
    		}
    		if (deep[x]>deep[y])	swap(x,y);
    		Tree.insert(1,1,n,dfn[x],dfn[y],z);
    	}
    	int query(int x,int y){//区间查询
    		int res=0;
    		while (top[x]!=top[y]){
    			if (deep[top[x]]<deep[top[y]])	swap(x,y);
    			res=(res+Tree.query(1,1,n,dfn[top[x]],dfn[x]))%mod;
    			x=fa[top[x]];
    		}
    		if (deep[x]>deep[y])	swap(x,y);
    		res=(res+Tree.query(1,1,n,dfn[x],dfn[y]))%mod;
    		return res;
    	}
    }T;
    int main(){
    	n=read(),m=read(),root=read(),mod=read();
    	for (int i=1;i<=n;i++)	v[i]=read();
    	for (int i=1,x,y;i<n;i++)	x=read(),y=read(),T.join(x,y),T.join(y,x);
    	T.build(root,1),T.dfs(root),Tree.build(1,1,n);
    	for (int i=1;i<=m;i++){
    		int t=read();
    		if (t==1){
    			int x=read(),y=read(),z=read();
    			T.insert(x,y,z);
    		}
    		if (t==2){
    			int x=read(),y=read();
    			printf("%d
    ",T.query(x,y));
    		}
    		if (t==3){//子树修改
    			int x=read(),z=read();
    			Tree.insert(1,1,n,dfn[x],dfn[x]+T.size[x]-1,z);
    		}
    		if (t==4){//子树查询
    			int x=read();
    			printf("%d
    ",Tree.query(1,1,n,dfn[x],dfn[x]+T.size[x]-1));
    		}
    	}
    	return 0;
    }
    
  • 相关阅读:
    2018福大软工实践第二次结对作业
    2018福大软工实践第一次结对作业
    《构建之法》第三章读书笔记
    《构建之法》第八章读书笔记
    Beta版本冲刺前准备
    项目测评
    Alpha版本事后诸葛亮
    Alpha版本冲刺(十)
    Alpha版本冲刺(九)
    Alpha版本冲刺(八)
  • 原文地址:https://www.cnblogs.com/Wolfycz/p/9459332.html
Copyright © 2011-2022 走看看