• 树上问题


    主要是根据学长的课件来透彻的。所以好多地方直接粘过来了qwq

    树链剖分

    所谓树链剖分,就是将树上的边进行划分。

    树链剖分有重链剖分,长链剖分,实链剖分等等。

    长链剖分是用来(O(1))(k)级祖先的,和优化一些树形DP,具体地来说是一些跟深度有关的DP。

    实链剖分是我们常说的(LCT(Link-Cut-Tree))

    本文介绍的主要是重链剖分。

    重链剖分

    既然是重链剖分,那么一定有重链和轻链,但是我们怎么来划分轻重链呢?

    我们定义:一个节点的所有子节点中(size)最大的那个节点为重儿子;那么这两个节点之间所连的边为一条重边。

    这样的话我们会得到一些性质。

    性质1:从根节点到叶子节点的路径上,跳重链的次数不会超过(O(logn)),从叶子节点到根节点,也成立

    证明:我们考虑,什么时候会跳重链,一定是当他需要向轻儿子走的时候废话。那么轻儿子的(size),一定小于他父亲节点

    (frac{1}{2}),那么不断走轻儿子,一直乘(frac{1}{2}),只需要跳(log n)次,就到(1)了,也就是叶子节点。

    首先考虑如何找到重儿子,我们知道,这个点的size就为他所有而儿子的size之和,且他儿子深度为当前点深度加1,递归的思路已经很明显了qwq。

    代码:

    int fa[maxn], dep[maxn], size[maxn], height_son[maxn];
    vector<int> v[maxn];
    inline void add(int x, int y){return (void)(v[x].push_back(y));}
    void bulid_poutree(int now){
    	size[now] = 1;
    	for(int i = 0; i < v[now].size(); i ++){
    		int to = v[now][i];
    		if(dep[to]) continue;
    		dep[to] = dep[now] + 1;//下一点的深度为当前点深度加一
    		fa[to] = now;
    		bulid_poutree(to);
    		size[now] += size[to];
    		if(size[to] > size[height_son[now]]) height_son[now] = to;//找到他的重儿子
    	}
    }
    

    树剖求LCA

    我们考虑,如果两个点在同一条重链上,那么肯定是深度小的是LCA。

    但是如果不在呢?

    我们是不是可以像倍增一下跳(也仅仅是像),跳重链,向上跳,然后一直跳到两个点在同一条重链上,再像刚才一样处理。

    让谁跳呢,深度大的?

    不不不,是(top)深度大的跳。

    因为,既然两个点不在同一条重链上,那么显然,他们的(top),也不在同一条重链上。

    那么类似于倍增,让(top)(fa),直到跳到同一条重链上。

    深度小的是LCA。

    如何往上跳,当两个点所在链的最高点不相同时他们一定不在同一条链上,根据这个条件while往上跳即可(让深度深的往上跳)

    while(top[x] != top[y]){
    	if(dep[top[x]] < dep[top[y]]) swap(x,y);
            /*......*/
    	x = fa[top[x]];
    }
    

    本人还不透彻,不如去写倍增LCA(其实倍增也不会qaq)

    升华

    前置芝士:线段树。

    先上一道例题来透彻一下树链剖分吧。

    好像不只是跳重链那么简单。

    在这里,我们引入一个叫做(dfs)序的概念。

    (dfs)序:我们在遍历整棵树时,每个节点被遍历到的时间戳(即这个节点是第几个被遍历到的)。

    1. 那么我们显然可以发现一颗子树内的(dfs)序是连续的,而且一条链上的(dfs)序也是连续的(可以画图理解)。

    1. 那么我们在询问一条路径时,就可以把这条路径分成好几条链,我们对于每条链分别统计即可。

    那么我们怎么才能维护每条链的信息呢?

    注意:一条链上的(dfs)序是连续的,那么问题转化为,如何维护一个区间的信息?

    当然是线段树啊。

    什么区间和,区间最值,区间覆盖,线段树简直不能再合适了。

    那么是不是这道题就做完了口牙。

    那么我们还可以保证我们树链剖分的时间复杂度为(O(nlog^2n))

    int fa[maxn], dep[maxn], size[maxn], top[maxn], dfn[maxn], id[maxn], height_son[maxn];
    /*其中dfn为该点所对应的dfs序,id为该点的dfs序所对应的自己原来的编号,top为这条重链上最高的点*/
    vector<int> v[maxn];
    inline void add(int x, int y){return (void)(v[x].push_back(y));}
    void bulid_poutree(int now){
    	size[now] = 1;
    	for(int i = 0; i < v[now].size(); i ++){
    		int to = v[now][i];
    		if(dep[to]) continue;
    		dep[to] = dep[now] + 1;//下一点的深度为当前点深度加一
    		fa[to] = now;
    		bulid_poutree(to);
    		size[now] += size[to];
    		if(size[to] > size[height_son[now]]) height_son[now] = to;//找到他的重儿子
    	}
    }
    
    void dfs(int now, int topfa){ //找到点对应的dfs序
    	top[now] = topfa;
    	dfn[now] = ++cnt;
    	id[cnt] = now;
    	if(height_son[now]) dfs(height_son[now], topfa); //先走重链
    	for(int i = 0; i < v[now].size(); i ++){//走轻链
    	        int to = v[now][i];
    		if(fa[now] == to or height_son[now] == to) continue;
    		dfs(to,to);
    	}
    }
    

    找到(dfs)序以后我们就把这个问题美滋滋的转化为了区间问题。

    对于(1,2)操作
    对于一条路径,我们根据2.将其剖为若干条链,每条链上的(dfs)序是连续的,那么我们就让深度高的点往上跳,同时记录每一条链或修改每一条链即可,最后不要忘了记录或修改两点在同一条链上的时候,而且我们要知道,同一条链上的两个点,深度高的(dfs)序大。

    对于(3,4)操作
    首先我们根据1.已经知道对于一颗子树上的(dfs)序是连续的,那么我们对子树的操作转到线段树上对(dfn[x])(dfn[x]+size[x]-1)进行常规的线段树修改和查询操作即可。

    代码真的是又臭又难写qaq。

    总结一下我提交了20次才A掉这道题时犯的各种错误主要还是我太蔡徐坤了qaq

    1. 如果用链式前向星存图记得开双倍空间,注意他是无向边。

    2. 线段树千万记得开4倍空间开4倍空间4倍空间指针请绕行

    3. 用线段树对我们剖开的树进行维护,在构建树时要从节点1开始,注意你构建的线段树与题中给的树是木有关系的,即这样

    tree.bulid_segtree(1,n,1);
    
    1. 线段树在修改和询问时一定要记得下放标记,并且要记得上传。

    2. 每个人的问题都不一样,还希望大家可以避免掉细节上的错误_(¦3」∠)_

    接下来上代码:

    #include <bits/stdc++.h>
    using namespace std;
    
    const int maxn = 1e5 + 10;
    
    int n, m, r, mod, cnt, a[maxn];
    
    struct segpou{
    	int fa[maxn], dep[maxn], size[maxn], top[maxn], dfn[maxn], id[maxn], height_son[maxn], head[maxn];
    	struct egde{
    		int y, nxt;
    	}e[maxn*2];
    	inline void add(int x, int y){
    		e[++cnt].y = y, e[cnt].nxt = head[x];
    		head[x] = cnt;
    		return;
    	}
    	void bulid_poutree(int now){//构建树剖所需要的信息
    		size[now] = 1;
    		for(int i = head[now]; i; i = e[i].nxt){
    			int to = e[i].y;
    			if(dep[to]) continue;
    			dep[to] = dep[now] + 1;
    			fa[to] = now;
    			bulid_poutree(to);
    			size[now] += size[to];
    			if(size[to] > size[height_son[now]]) height_son[now] = to;
    		}
    	}
    	void dfs(int now, int topfa){//找到dfs序,且先走重边
    		top[now] = topfa;
    		dfn[now] = ++cnt;
    		id[cnt] = now;
    		if(height_son[now]) dfs(height_son[now], topfa);
    		for(int i = head[now]; i; i = e[i].nxt){
    			int to = e[i].y;
    			if(fa[now] == to or height_son[now] == to) continue;
    			dfs(to,to);
    		}
    	}
    
    	#define ls (now << 1)
    	#define rs (now<<1|1)
    	#define mid ((l+r)>>1)
    
    	struct node{
    		int l, r, sum, tag;
    		inline int get(){
    			return ((sum%mod)+(tag*(r-l+1)%mod))%mod;
    		}
    	}no[maxn*4];
    	void up(int now){
    		no[now].sum = ((no[ls].get()%mod) + (no[rs].get()%mod))%mod;
    		return;
    	}
    	void down(int now){
    		no[ls].tag += no[now].tag;
    		no[rs].tag += no[now].tag;
    		no[now].tag = 0;	
    		return;
    	}
    	void bulid_segtree(int l, int r, int now){
    		no[now].l = l, no[now].r = r;
    		if(l == r) return (void)(no[now].sum = a[id[l]]);
    		bulid_segtree(l,mid,ls), bulid_segtree(mid+1,r,rs);
    		up(now);
    	}
    	void chenge(int l, int r, int now, int val){
    		if(r < no[now].l or no[now].r < l) return;
    		if(l <= no[now].l and no[now].r <= r) return(void)(no[now].tag += val);
    		down(now);
    		chenge(l,r,ls,val), chenge(l,r,rs,val);
    		up(now);
    	}
    	void query(int l, int r, int now, int &ans){
    		if(r < no[now].l or no[now].r < l) return;
    		if(l <= no[now].l and no[now].r <= r){
    			ans = (ans%mod+no[now].get()%mod)%mod;
    			return;
    		}
    		down(now);
    		query(l,r,ls,ans), query(l,r,rs,ans);
    		up(now);
    	}
    	int poutree_query(int x, int y){
            /*对于两个点不在同一条链上时,我们选择让更深的点往上跳,直到在同一条链上为止*/
    		int res = 0;
    		while(top[x] != top[y]){
    			if(dep[top[x]] < dep[top[y]]) swap(x,y);
    			int tmp = 0;
    			query(dfn[top[x]],dfn[x],1,tmp);
    			res = (res%mod+tmp%mod)%mod;
    			x = fa[top[x]];
    		}
    		if(dep[x] < dep[y]) swap(x,y);
    		int tmp = 0;
    		query(dfn[y],dfn[x],1,tmp);
    		res = (res%mod+tmp%mod)%mod;
    		return res;
    	}
    	void poutree_chenge(int x, int y, int val){
            /*修改也是如此*/
    		while(top[x] != top[y]){
    			if(dep[top[x]] < dep[top[y]]) swap(x,y);
    			chenge(dfn[top[x]],dfn[x],1,val);
    			x = fa[top[x]];
    		}
    		if(dep[x] < dep[y]) swap(x,y);
    		chenge(dfn[y],dfn[x],1,val);
    	}
    }tree;
    
    signed main(){
    	scanf("%d%d%d%d", &n, &m, &r, &mod);
    	for(int i = 1; i <= n; i ++){
    		scanf("%d", &a[i]);
    	}
    	for(int i = 1, x, y; i < n; i ++){
    		scanf("%d%d", &x, &y);
    		tree.add(x,y); tree.add(y,x);
    	}
    	cnt = 0, tree.dep[r] = 1;
    	tree.bulid_poutree(r);
    	tree.dfs(r,r);
    	tree.bulid_segtree(1,n,1);
    	for(int cmp, x, y, z; m; m --){
    		scanf("%d", &cmp);
    		if(cmp == 1){
    			scanf("%d%d%d", &x, &y, &z);
    			tree.poutree_chenge(x,y,z);
    		}
    		if(cmp == 2){
    			scanf("%d%d", &x, &y);
    			printf("%d
    ",tree.poutree_query(x,y));
    		}
    		if(cmp == 3){
    			scanf("%d%d", &x, &y);
    			tree.chenge(tree.dfn[x],tree.dfn[x]+tree.size[x]-1,1,y);
    		}
    		if(cmp == 4){
    			scanf("%d", &x);
    			int ans = 0;
    			tree.query(tree.dfn[x],tree.dfn[x]+tree.size[x]-1,1,ans);
    			printf("%d
    ", ans);
    		}
    	}
    	return 0;
    }
    
    

    树上差分

    占坑qwq

    各种树上问题

    P3398 仓鼠找sugar

    P1967 货车运输

  • 相关阅读:
    jenkins
    k8s 驱逐限制
    jenkins+k8s 实现持续集成
    镜像更新备份
    【工具分享】2020年4月 phpstorm2020.1 for mac
    【排坑】mac安装homebrew会遇到的各种问题解决方案
    记一次C盘扩容
    2018夏季工作台之再设计
    left join后面加上where条件浅析
    编程随想篇(2018夏)
  • 原文地址:https://www.cnblogs.com/Vanyun/p/13286973.html
Copyright © 2020-2023  润新知