树链剖分

原理:将一棵树剖分成一条条的链,从而降低时间复杂度

这两篇写得能够理解了:

前置芝士

首先得会一个线段树,完成剖分后,用来维护每一条的信息。

#include <bits/stdc++.h>
typedef int intt;
#define int long long
#define lc k << 1
#define rc k << 1 | 1
const int M = 2e6 + 10;
using namespace std;
int n,m,ans;
int a[M];
struct node{
	int l,r;
	int sum;
	int lazy;
}t[M << 2];
void Pushup(int k){
	t[k].sum = t[lc].sum + t[rc].sum;
}
void Pushdown(int k){
	if(t[k].lazy){
		t[lc].lazy += t[k].lazy;
		t[rc].lazy += t[k].lazy;
		t[lc].sum += (t[lc].r - t[lc].l + 1) * t[k].lazy;
		t[rc].sum += (t[rc].r - t[rc].l + 1) * t[k].lazy;
		t[k].lazy = 0;
	}
}
void Build(int k,int l,int r){
	t[k].l = l,t[k].r = r;
	if(l == r){
		t[k].sum = a[l];
		return ;
	}
	int mid = (l + r) >> 1;
	Build(lc,l,mid);
	Build(rc,mid + 1,r);
	Pushup(k);
}
void Modify(int k,int l,int r,int d){
	if(t[k].r < l || t[k].l > r) return;
	if(t[k].l >= l && t[k].r <= r){
		t[k].lazy += d;
		t[k].sum += (t[k].r - t[k].l + 1) * d;
		return ;
	}
	Pushdown(k);
	Modify(lc,l,r,d);
	Modify(rc,l,r,d);
	Pushup(k);
}
void Query(int k,int l,int r){
	if(t[k].r < l || t[k].l > r) return;
	if(t[k].l >= l && t[k].r <= r){
		ans += t[k].sum;
		return ;
	}
	Pushdown(k);
	Query(lc,l,r);
	Query(rc,l,r);
}
intt main(){
	cin >> n >> m;
	for(int i = 1;i <= n;i++) cin >> a[i];
	Build(1,1,n);
	while(m--){
		int opt;
		cin >> opt;
		if(opt == 1){
			int x,y,k;
			cin >> x >> y >> k;
			Modify(1,x,y,k);
		}
		else {
			int x,y;
			cin >> x >> y;
			Query(1,x,y);
			cout << ans << "\n"; 
			ans = 0;
		}
	}
	return 0;
}

可以封装起来,以便后面使用

两次dfs

第一次 \(dfs\) 记录以下信息:

  1. 每个节点的父亲
  2. 每个节点的重儿子
  3. 每个节点的深度
  4. 每个节点的大小

第二次 \(dfs\) 记录以下信息:

  1. 节点权值的 \(dfs\)
  2. 及其时间戳
  3. 当前节点所在的重链的头,(头的头是自己)

于是需要以下数组:

int fa[Maxn];//记录父节点 
int depth[Maxn];//记录节点深度 
int son[Maxn];//记录重儿子
int sz[Maxn];//记录以该节点为根节点的树的大小(节点个数包括根节点) 
int top[Maxn];//每一个节点所属重链的根节点 
int dfn[Maxn];//每一个节点的时间戳
int w[Maxn];//dfs序后节点的权值,用线段树维护
int sign = 0;//时间戳计数器
int v[Maxn];//存放所有节点的权值 

展示一下第一次的 \(dfs\) 的代码:(还是比较常规的

void Dfs1(int u,int fath){
	fa[u] = fath;
	depth[u] = depth[fath] + 1;
	sz[u] = 1;
	int maxsize_son = -1; 
	for(int i = head[u];~i;i = edge[i].nxt){
		int v = edge[i].to;
		if(v == fath) continue;
		Dfs1(v,u);
		
		sz[u] += sz[v];
		if(sz[v] > maxsize_son){
			maxsize_son = sz[v];
			son[u] = v;
		}
	}
}

接着就是第二次的 \(dfs\)\(code\)

void Dfs2(int u,int fath){
	dfn[u] = ++sign;
	top[u] = fath;//u所属重链的祖先节点
	w[sign] = v[u];//dfs序后的节点权值
	if(!son[u]) return ;
	Dfs2(son[u],fath);
	for(int i = head[u];~i;i = edge[i].nxt){
		int v = edge[i].to;
		if(v == fa[u] || v == son[u]) continue;
		Dfs2(v,v);
	}
}

线段树

线段树没什么好改的,就如树剖模版,加个取模即可

操作

引理:除根结点外的任何一个结点的父亲结点都一定在一条重链上。
证明:因为父亲结点存在儿子,所以一定存在重儿子,所以一定在一条重链上。

  1. 操作 \(1\) 和 操作 \(2\)
    如果要查询的两个结点在同一条重链上,问题就变得非常简单,只需要在线段树上查询就好啦。
    如果不在一条重链上呢?
    我们可以维护两个指,指向两个结点不停地让所在链的顶部结点深度较大的指针沿着所在重链往上跳
    具体就是指针 \(p\) 从当前跳到 \(top[p]\)
    一边跳一边在线段树上操作(因为连续)
    加完之后p往上跳到当前的父亲结点处由上定理可知:\(p\) 还是在一条重链上
    于是无限循环,直到两指针跳到同一结点或同一重链上,就解决了问题。
//操作1
void Modify_path(int x,int y,int d){
	while(top[x] != top[y]){
		if(depth[top[x]] < depth[top[y]]) swap(x,y);
		Modify(1,dfn[top[x]],dfn[x],d);
		x = fa[top[x]];
	}
	if(depth[x] > depth[y]) swap(x,y);
	Modify(1,dfn[x],dfn[y],d);
}
//操作2
void Query_path(int x,int y){
	while(top[x] != top[y]){
		if(depth[top[x]] < depth[top[y]]) swap(x,y);
		Query(1,dfn[top[x]],dfn[x]);
		ans %= p;
		x = fa[top[x]];
	}
	if(depth[x] > depth[y]) swap(x,y);
	Query(1,dfn[x],dfn[y]);
}
  1. 操作 \(3\) 和操作 \(4\)
    没什么好说的,直接看代码
//操作3
void Modify_son(int x,int d){
	Modify(1,dfn[x],dfn[x] + sz[x] - 1,d);
}
//操作4
void Query_son(int x){
	Query(1,dfn[x],dfn[x] + sz[x] - 1);
}

完整代码

#include <bits/stdc++.h>
typedef int intt;
#define int long long
const int M = 2e6 + 10;
using namespace std;
int n,m,p,rt,ans;
int v[M];
struct node{
	int nxt,to;
}edge[M << 2];
int cnt;
int head[M];
void Addedge(int u,int v){
	edge[++cnt].to = v;
	edge[cnt].nxt = head[u];
	head[u] = cnt;
}
int depth[M],sz[M],fa[M],son[M];
void Dfs1(int u,int fath){
	fa[u] = fath;
	depth[u] = depth[fath] + 1;
	sz[u] = 1;
	int maxsize_son = -1;
	for(int i = head[u];i;i = edge[i].nxt){
		int v = edge[i].to;
		if(v == fath) continue;
		
		Dfs1(v,u);
		sz[u] += sz[v];
		if(sz[v] > maxsize_son){
			maxsize_son = sz[v];
			son[u] = v;
		}
	}
}
int sign;
int dfn[M],top[M],w[M];
void Dfs2(int u,int fath){
	dfn[u] = ++sign;
	top[u] = fath;
	w[sign] = v[u];
	if(!son[u]) return ;
	Dfs2(son[u],fath);
	for(int i = head[u];i;i = edge[i].nxt){
		int v = edge[i].to;
		if(v == son[u] || v == fa[u]) continue;
		Dfs2(v,v);
	}
}
#define lc k << 1
#define rc k << 1 | 1
struct Node{
	int l,r;
	int sum;
	int lazy;
}t[M];
void Pushup(int k){
	t[k].sum = t[lc].sum + t[rc].sum,t[k].sum %= p;
}
void Pushdown(int k){
	if(t[k].lazy){
		t[lc].lazy += t[k].lazy,t[lc].lazy %= p;
		t[rc].lazy += t[k].lazy,t[rc].lazy %= p;
		t[lc].sum += (t[lc].r - t[lc].l + 1) * t[k].lazy,t[lc].sum %= p;
		t[rc].sum += (t[rc].r - t[rc].l + 1) * t[k].lazy,t[rc].sum %= p;
		t[k].lazy = 0;
	}
}
void Build(int k,int l,int r){
	t[k].l = l,t[k].r = r;
	if(l == r){
		t[k].sum = w[l],t[k].sum %= p;
		return ;
	}
	int mid = (l + r) >> 1;
	Build(lc,l,mid);
	Build(rc,mid + 1,r);
	Pushup(k);
}
void Modify(int k,int l,int r,int d){
	if(t[k].r < l || t[k].l > r) return ;
	if(t[k].l >= l && t[k].r <= r){
		t[k].lazy += d,t[k].lazy %= p;
		t[k].sum += (t[k].r - t[k].l + 1) * d,t[k].sum %= p;
		return ;
	}
	Pushdown(k);
	Modify(lc,l,r,d);
	Modify(rc,l,r,d);
	Pushup(k);
}
void Query(int k,int l,int r){
	if(t[k].r < l || t[k].l > r) return;
	if(t[k].l >= l && t[k].r <= r){
		ans += t[k].sum,ans %= p;
		return ;
	}
	Pushdown(k);
	Query(lc,l,r);
	Query(rc,l,r);
}
void Modify_path(int x,int y,int d){
	while(top[x] != top[y]){
		if(depth[top[x]] < depth[top[y]]) swap(x,y);
		Modify(1,dfn[top[x]],dfn[x],d);
		x = fa[top[x]];
	}
	if(depth[x] > depth[y]) swap(x,y);
	Modify(1,dfn[x],dfn[y],d);
}
void Modify_son(int x,int d){
	Modify(1,dfn[x],dfn[x] + sz[x] - 1,d);
}
void Query_path(int x,int y){
	while(top[x] != top[y]){
		if(depth[top[x]] < depth[top[y]]) swap(x,y);
		Query(1,dfn[top[x]],dfn[x]);
		ans %= p;
		x = fa[top[x]];
	}
	if(depth[x] > depth[y]) swap(x,y);
	Query(1,dfn[x],dfn[y]);
}
void Query_son(int x){
	Query(1,dfn[x],dfn[x] + sz[x] - 1);
}
intt main(){
	cin >> n >> m >> rt >> p;
	for(int i = 1;i <= n;i++) cin >> v[i];
	for(int i = 1;i < n;i++){
		int u,v;
		cin >> u >> v;
		Addedge(u,v),Addedge(v,u);
	}
	Dfs1(rt,-1);
	Dfs2(rt,rt);
	Build(1,1,n);
	while(m--){
		int opt;
		cin >> opt;
		if(opt == 1){
			int x,y,z;
			cin >> x >> y >> z;
			Modify_path(x,y,z);
		}
		if(opt == 2){
			int x,y;
			cin >> x >> y;
			Query_path(x,y);
			cout << ans << "\n";
			ans = 0; 
		}
		if(opt == 3){
			int x,z;
			cin >> x >> z;
			Modify_son(x,z);
		}
		if(opt == 4){
			int x;
			cin >> x;
			Query_son(x);
			cout << ans << "\n";
			ans = 0; 
		}
	}
	return 0;
}

\(LCA\)

附赠树剖版的 \(LCA\)

int Lca(int x,int y){
	while(top[x] != top[y]){
		if(depth[top[x]] > depth[top[y]]) x = fa[top[x]];
		else y = fa[top[y]];
	}
	if(depth[x] < depth[y]) return x;
	else return y;
}
posted @ 2024-09-02 14:54  Nefert_zlm  阅读(41)  评论(0)    收藏  举报