树链刨分

树链刨分

目录

发现漏了树剖的笔记,来补作业了==

有关dfs序

老玩意,就是遍历树的时候每个节点被搜到的顺序

我们画出一棵树,模拟一下搜索过程可以得出这么一个结论:

一棵子树在dfs序上是连续的一段

每个节点对应着一个区间,对节点操作就可以用线段树维护了

树链剖分

概念:

重儿子(节点):子树结点数目最多的结点

轻儿子(节点):除了重儿子以外的结点

重边:父亲结点和重儿子连成的边

轻边:父亲节点和轻儿子连成的边

重链:由多条重边连接而成的路径

轻链:由多条轻边连接而成的路径

树链刨分

图来自:Ivanovcraft

根据上面的定义:

我们可以得出:加粗的黑色节点是重儿子,其余的都是轻儿子(叶子节点没有重儿子,非叶子节点只有一个重儿子)

红的是重边,黑的是轻边

几个变量

\(f[u]\) 结点 \(u\) 的父亲节点

\(dep[u]\) 保存结点 \(u\) 的深度值

\(siz[u]\) 保存以 \(u\) 为根的子树节点个数

\(son[u]\) \(u\) 的重儿子

\(pre[u]\) 当前 \(dfs\) 标号在树中所对应的节点

\(top[u]\) 当前节点所在链的顶端节点

\(dfn[u]\) 树中每个节点剖分以后的新编号(DFS的执行顺序)

第一个大法师

预处理,求 \(f[u]\),\(dep[u]\),\(son[u]\) ,\(siz[u]\)

看代码应该就能理解

	void dfs(int x,int f,int d){
	  dep[x] = d,fa[x] = f,siz[x] = 1;
	  for(int i = head[x]; i;i = e[i].nxt){
	  	   int v = e[i].v;
	  	   if(v != fa[x]){
	  	       dfs(v, x, d + 1);
			   siz[x] += siz[v];
			   if(!son[x]||siz[son[x]] < siz[v])
			   	  son[x] = v; 	  
		   }
	  }
	}

第二个大法师

把树剖了

按照每个点的重儿子向下搜,跑出一个 \(dfs\) 序,连续的 一段 \(dfs\) 就是一条重链,顺便存下在这个 \(dfs\) 序下,每个\(dfs\) 序在原树中的标号 \(pre[u]\)(方便建线段树的时候确定每个点在原树中的位置)

跑出来应该是这样的

    void dfs2(int x,int tp){
    	dfn[x]=++cnt,pre[cnt] = x,top[x] = tp;
		if(son[x]) dfs2(son[x],tp);
		for(int i = head[x]; i; i = e[i].nxt){
			 int v = e[i].v;
			 if(v != fa[x]&&v != son[x]) dfs2(v,v);
		}
	}

线段树维护

见很多博客都没有写这个(一开始也是没有搞懂),所以这里提一下

线段树维护的是每个重链的信息,也就是说线段树的标号对应着第二个 \(dfs\) 跑出来的 \(dfs\) 序,这样每条重链上的信息就可以直接用线段树维护了(因为它们的 \(dfs\) 序都是连续的)例如:求一条重链上的点权值和,直接利用线段树区间加就好了

求树上两点之间最短路径

如果两点在一条重链上:直接区间利用两点的 \(dfs\) 序区间求和就好了

如果不在一条重链上:先把两点跳到一条重链上,再求两点在这条重链上的距离就好了

也可以利用这个方法求两点的 \(LCA\)

  int asksum(int x,int y){
		int ans = 0;
		while(top[x] != top[y]){
			if(dep[top[x]] < dep[top[y]]) swap(x,y);
				ans = (ans + Seg::query(1, 1, n, dfn[top[x]], dfn[x])) % mod;
		    x = fa[top[x]];
		}
		if(dep[x] > dep[y]) swap(x,y);
		ans = (ans + Seg::query(1, 1, n, dfn[x], dfn[y])) % mod;
		return ans;
	}

更改两点间最短路径上的值

和上面一个样,只不过把求和变成了区间更改

   void change_sum(int x,int y,int k){
		while(top[x] != top[y]){
			if(dep[top[x]] < dep[top[y]]) swap(x,y);
			Seg::update(1, k, 1, n,dfn[top[x]], dfn[x]);
		    x = fa[top[x]];
		}
		if(dep[x] > dep[y]) swap(x,y);
		Seg::update(1, k, 1, n,dfn[x], dfn[y]);
   } 

求一个点子树的值

每条重链的 \(dfs\) 是连续的,所以每个点的子树的 \(dfs\) 也是连续的(手摸一下),所以求每个点子树的值,只需要求这个点的 \(dfn[x]\) 序到 \(dfn[x] + siz[x] - 1\) 就好了

   int query_tree(int x){
   	   return Seg::query(1, 1, n, dfn[x],dfn[x] + siz[x] - 1);
   }

时间复杂度:

\(O(nlog^2n)\)

树链剖分模板

已知一棵包含 \(N\) 个结点的树(连通且无环),每个节点上包含一个数值,需要支持以下操作:

操作 1:将树从 \(x\) 到 \(y\) 结点最短路径上所有节点的值都加上 \(z\)

操作 2:求树从 \(x\) 到 \(y\) 结点最短路径上所有节点的值之和。

操作 3: 将以 \(x\) 为根节点的子树内所有节点值都加上 \(z\)

操作 4: 求以 \(x\) 为根节点的子树内所有节点值之和

/*
work by:Ariel
*/
#include <iostream>
#include <cstdio>
#define  N  100005
#define ll long long
#define lson rt << 1
#define rson rt << 1|1

using namespace std;
int n,m,r;
ll mod;
int pre[N],w[N],dep[N],fa[N],son[N],siz[N],top[N],dfn[N],dfn2[N],cnt;
ll read(){
   ll x = 0,f = 1;char c = getchar();
   while(c < '0'||c > '9'){if(c == '-')f = -1;c = getchar();}
   while(c >= '0'&&c <= '9'){x = x*10 + c - '0';c = getchar();}
   return f*x;
}

namespace Seg{
	struct Tree{
		int sum,len,lazy;
	}tree[N << 1];
	void push_up(int rt){
		tree[rt].sum = (tree[lson].sum + tree[rson].sum) % mod;
	}
	void build(int rt,int l,int r){
		tree[rt].len = r - l + 1;
		if(l == r){
			tree[rt].sum = w[pre[l]];
			return;
		} 
		int mid = (l + r)>>1;
		build(lson, l, mid);
		build(rson, mid + 1, r);
		push_up(rt);
	}
	void pushdown(int rt) {
		if (tree[rt].lazy){
		  tree[lson].sum = (tree[lson].sum + (tree[rt].lazy * tree[lson].len) % mod) % mod;
		  tree[rson].sum = (tree[rson].sum + (tree[rt].lazy * tree[rson].len) % mod) % mod;
		  tree[lson].lazy = (tree[lson].lazy + tree[rt].lazy) % mod;
		  tree[rson].lazy = (tree[rson].lazy + tree[rt].lazy) % mod;
		  tree[rt].lazy = 0;	
		}
	}
	void update(int rt,int k,int l,int r,int L,int R){
		if(l >= L && r <= R){
			tree[rt].sum = (tree[rt].sum + (k * tree[rt].len) % mod) % mod;
			tree[rt].lazy = (tree[rt].lazy + k) % mod;
			return;
		}
		pushdown(rt);
		int mid = (l + r) >> 1;
		if(mid >= L) update(lson, k, l, mid, L, R);
		if(mid < R) update(rson, k, mid+1, r, L, R);
		push_up(rt);
	}
	int query(int rt,int l,int r,int L,int R){
		if(l >= L&& r <= R) return tree[rt].sum;
		pushdown(rt);
		int mid = (l + r)>>1,ans = 0;
		if(L <= mid) ans = (ans + query(lson,l,mid,L,R) % mod) % mod;
		if(mid < R)  ans = (ans + query(rson,mid + 1,r,L,R) % mod) % mod;
		return ans;
	}
}
namespace Cut {
	struct edge{
		int v,nxt;
	}e[N << 1];
	int head[N],js;
	void add_edge(int u,int v){
     e[++js].v = v;e[js].nxt = head[u];head[u] = js;
	}
	void dfs(int x,int f,int d){
	  dep[x] = d,fa[x] = f,siz[x] = 1;
	  for(int i = head[x]; i;i = e[i].nxt){
	  	   int v = e[i].v;
	  	   if(v != fa[x]){
	  	       dfs(v, x, d + 1);
			   siz[x] += siz[v];
			   if(!son[x]||siz[son[x]] < siz[v])
			   	  son[x] = v; 	  
		   }
	  }
	}
    void dfs2(int x,int tp){
    	dfn[x]=++cnt,pre[cnt] = x,top[x] = tp;
		if(son[x]) dfs2(son[x],tp);
		for(int i = head[x]; i; i = e[i].nxt){
			 int v = e[i].v;
			 if(v != fa[x]&&v != son[x]) dfs2(v,v);
		}
	}
   void change_tree(int x,int k){
   	    Seg::update(1 , k, 1, n, dfn[x],dfn[x] + siz[x] - 1);
   }
   int query_tree(int x){
   	   return Seg::query(1, 1, n, dfn[x],dfn[x] + siz[x] - 1);
   }
   void change_sum(int x,int y,int k){
		while(top[x] != top[y]){
			if(dep[top[x]] < dep[top[y]]) swap(x,y);
			Seg::update(1, k, 1, n,dfn[top[x]], dfn[x]);
		    x = fa[top[x]];
		}
		if(dep[x] > dep[y]) swap(x,y);
		Seg::update(1, k, 1, n,dfn[x], dfn[y]);
   } 
   int asksum(int x,int y){
		int ans = 0;
		while(top[x] != top[y]){
			if(dep[top[x]] < dep[top[y]]) swap(x,y);
				ans = (ans + Seg::query(1, 1, n, dfn[top[x]], dfn[x])) % mod;
		    x = fa[top[x]];
		}
		if(dep[x] > dep[y]) swap(x,y);
		ans = (ans + Seg::query(1, 1, n, dfn[x], dfn[y])) % mod;
		return ans;
	}
}
int main() {
	n = read(), m = read(),r = read(),mod = read();
	for (int i = 1; i <= n; i++) w[i] = read();
	for (int i = 1, x, y; i <= n - 1; i++) {
	  x = read(), y = read(); 
	  Cut::add_edge(x, y); 
	  Cut::add_edge(y, x);
	}
	Cut::dfs(r, 0, 1);
	Cut::dfs2(r, r);
	Seg::build(1, 1, n);
		for (int i = 1, opt, x, y,z; i <= m; i++) {
		opt = read();
		if (opt == 1) x = read(), y = read(),z = read(),Cut::change_sum(x, y, z);
	    if (opt == 2) x = read(),y = read(),printf("%lld\n", Cut::asksum(x, y));
		if (opt == 3) x = read(),y = read(),Cut::change_tree(x,y);
		if (opt == 4) x = read(),printf("%lld\n", Cut::query_tree(x));
	}
}
上一篇:E. New Reform(dfs判环/并查集判环)


下一篇:OAuth 2.0的一个简单解释