1. 程式人生 > 實用技巧 >[筆記][題解]樹鏈剖分&lgP3384

[筆記][題解]樹鏈剖分&lgP3384

[筆記]樹鏈剖分

演算法概述

​ 樹鏈剖分就是把一棵樹分成幾條不相交的鏈來做.

變數定義

重兒子:某個節點(非葉子節點)的子樹中,節點個數最多的子樹的根節點(即與這個點相連的點)就是這個點的重兒子.

輕兒子:對於一個非葉子節點,它的兒子中非重兒子的剩下所有兒子就是輕兒子,也就是說葉子節點沒有輕兒子或重兒子.

重邊:連線某個節點和它的重兒子的邊

重鏈:由許多重邊所構成的鏈

輕鏈:由許多非重邊構成的鏈

這樣,對於一個節點,找出了它的重兒子,這棵樹就被自然的拆成了許多重鏈和輕鏈.

演算法詳述

·顯然,我們需要維護這些鏈,那麼就要對所有鏈上的點用\(dfs\)進行重新編號.

dfs1

task:

1.標記每個點的深度dep[i];

2.標記每個點的父親fa[i];

3.標記每個非葉子節點的字數大小(包含根節點).

4.標記每個非葉子節點的重兒子編號son[i].

void dfs1(int x,int f,int depth){//x為當前節點,f為爸爸,depth為深度
	dep[x] = depth;
    fa[x] = f;
    size[x] = 1;//包含自己的兒子的個數
    int heavyson_size = -1;//記錄重兒子的兒子個數
    for(int i = fir[x];i;i = edge[i].next){
        if(edge[i].to == f)continue;//如果搜到的是父親就跳過
        dfs1(edge[i].to,x,dep + 1);
        size[x] += size[edge[i].to];
        if(size[edge[i].to] > heavyson_size){
            son[x] = edge[i].to;
            heavyson_size = size[edge[i].to];
        }
    }
}

dfs2

task:

1.標記每個點的新編號

2.將每個點的初始值賦給新的編號上

3.處理每個點所在鏈的頂端

4.處理每條鏈

注意:這裡要先處理重兒子再處理輕兒子,因為在標新標號時是重兒子優先的.標號如圖

同時我們可以發現,由於是進行\(dfs\),所以每一個子樹的編號是連續的

void dfs2(int x,int topp){//topp是當前鏈的最頂端的節點
    id[x] = ++cnt;//新的編號
    wt[cnt] = w[x];//把當前點的初值賦給新的編號
    if(!son[x])return;//如果沒有重兒子就返回
    dfs2(son[x],topp);//要先處理重兒子
    for(int i = fir[x];i;i = edge[i].next){
        if(edge[i].to == fa[x] || edge[i].to == son[x])//如果搜到了重兒子就跳過,因為在之前就搜過了.
            	continue;
       dfs2(y,y);//每個輕兒子都有一條從自己開始的輕鏈
    }
}

解決問題

一.處理任意兩點間路徑上的點權和

​ 1.設所在鏈的鏈頂深度更深的那個點為\(x\),\(ans\)加上\(x\)點到\(x\)所在鏈頂端這一段區間的點權和.

​ 2.把\(x\)調到\(x\)所在鏈頂端的那個點的父親節點.

​ 3.重複一上步驟,直到兩個點在同一條鏈上

程式碼實現:

int res;
int ask_range(int x,int y){
    int ans = 0;
    while(top[x] != top[y]){//兩個點不在同一條鏈上.
        if(dep[top[x]] < dep[top[y]])swap(x,y);//意思是上面的第1步.
        res = 0;
        ask(1,1,n,id[top[x]],id[x]);//求到鏈頂的這一段區間的點權和.
        ans += res;ans %= mod;
        x = fa[top[x]];//意思是上述的第2步.
    }
    //現在連個點都在同一條鏈上.
    if(dep[x] > dep[y])swap(x,y);
    res = 0;
    ask(1,1,n,id[x],id[y]);//加上連個點之間的點權和.
}

二.處理一個點及它子樹的點權和

​ 比較簡單,因為每個子樹的編號都是連續的.

​ 所以,如果某個子樹的根節點是\(x\),那麼這個子樹的編號左端點就是id[x],右端點就是id[x] + size[x] - 1.

程式碼:

int ask_son(int x){
    res = 0;
    ask(1,1,n,id[x],id[x] + size[x] - 1);
    return res;
}

三.區間修改

​ 類似於線段樹,與問題一差不多.

void change_range(int x,int y,int k){//區間修改
    k %= mod;
    while(top[x] != top[y]){
        if(dep[top[x]] < dep[top[y]])swap(x,y);
        change(1,1,n,id[top[x]],id[x],k);
        x = fa[top[x]];
    }
    if(dep[x] > dep[y])swap(x,y);
    change(1,1,n,id[x],id[y],k)
}
void change_son(int x,int k){//修改子樹
    change(1,1,n,id[x],id[x] + size[x] - 1,k);
}

到這裡,樹鏈剖分的基本用途和實現就講完了,放上完整程式碼:

#include <bits/stdc++.h>
using namespace std;
struct node{
	int to,next;
}edge[1000010 * 4];
struct seg_tree{
	int l,r,tag,w;
}tree[1000010 * 4];
int fir[1000010],tot,root;
int n,m,r,mod,w[1000010];
int dep[1000010],res,fa[1000010],top[1000010],son[1000010];
int wt[1000010],id[1000010],size[1000010],cnt;
void pushdown(int num){
	tree[num * 2].tag += tree[num].tag;
	tree[num * 2 + 1].tag += tree[num].tag;
	tree[num * 2].w += tree[num].tag * (tree[num * 2].r - tree[num * 2].l + 1);
	tree[num * 2 + 1].w += tree[num].tag * (tree[num * 2 + 1].r - tree[num * 2 + 1].l + 1);
	tree[num * 2].w %= mod;
	tree[num * 2 + 1].w %= mod;
	tree[num].tag = 0;
}
void add(int x,int y){
	edge[++tot].to = y;
	edge[tot].next = fir[x];
	fir[x] = tot;
	return;
}
void build(int num,int l,int r){
	tree[num].l = l;tree[num].r = r;tree[num].tag = 0;
	if(l == r){
		tree[num].w = wt[l];
		tree[num].w %= mod;
		return;
	}
	int mid = (l + r) / 2;
	build(num * 2,l,mid);
	build(num * 2 + 1,mid + 1,r);
	tree[num].w = tree[num * 2].w + tree[num * 2 + 1].w;
	tree[num].w %= mod;
}
void ask(int num,int tar_l,int tar_r){
	if(tree[num].l >= tar_l && tree[num].r <= tar_r){
		res += tree[num].w;
		res %= mod;
		return;
	}
	pushdown(num);
	int mid = (tree[num].l + tree[num].r) / 2;
	if(tar_l <= mid)
		ask(num * 2,tar_l,tar_r);
	if(tar_r > mid)
		ask(num * 2 + 1,tar_l,tar_r);
}
void change(int num,int tar_l,int tar_r,int k){
	if(tree[num].l >= tar_l && tree[num].r <= tar_r){
		tree[num].tag += k;
		tree[num].w += k * (tree[num].r - tree[num].l + 1);
		tree[num].w %= mod;
		return;
	}
	pushdown(num);
	int mid = (tree[num].l + tree[num].r) / 2;
	if(tar_l <= mid)
		change(num * 2,tar_l,tar_r,k);
	if(mid < tar_r)
		change(num * 2 + 1,tar_l,tar_r,k);
	tree[num].w = tree[num * 2].w + tree[num * 2 + 1].w;
	tree[num].w %= mod;
}
int ask_range(int x,int y){
	int ans = 0;
	while(top[x] != top[y]){
		if(dep[top[x]] < dep[top[y]])swap(x,y);
		res = 0;
		ask(1,id[top[x]],id[x]);
		ans += res;
		ans %= mod;
		x = fa[top[x]];
	}
	if(dep[x] > dep[y])swap(x,y);
	res = 0;
	ask(1,id[x],id[y]);
	ans += res;
	ans %= mod;
	return ans;
}
void change_range(int x,int y,int k){
	k %= mod;
	while(top[x] != top[y]){
		if(dep[top[x]] < dep[top[y]])swap(x,y);
		change(1,id[top[x]],id[x],k);
		x = fa[top[x]];
	}
	if(dep[x] > dep[y])swap(x,y);
	change(1,id[x],id[y],k);
}
int ask_son(int x){
	res = 0;
	ask(1,id[x],id[x] + size[x] - 1);
	return res;
}
void change_son(int x,int k){
	change(1,id[x],id[x] + size[x] - 1,k);
}
void dfs1(int x,int f,int depth){
	dep[x] = depth;
	fa[x] = f;
	size[x] = 1;
	int heavyson_size = -1;
	for(int i = fir[x];i;i = edge[i].next){
		if(edge[i].to == f)continue;
		dfs1(edge[i].to,x,depth + 1);
		size[x] += size[edge[i].to];
		if(size[edge[i].to] > heavyson_size){
			heavyson_size = size[edge[i].to];
			son[x] = edge[i].to;
		}
	}
}
void dfs2(int x,int topp){
	id[x] = ++cnt;
	wt[cnt] = w[x];
	top[x] = topp;
	if(!son[x])return;
	dfs2(son[x],topp);
	for(int i = fir[x];i;i = edge[i].next){
		if(edge[i].to == fa[x] || edge[i].to == son[x])continue;
			dfs2(edge[i].to,edge[i].to);
	}
}
int main(){
	scanf("%d%d%d%d",&n,&m,&root,&mod);
	for(int i = 1;i <= n;i++)scanf("%d",&w[i]);
	for(int i = 1;i < n;i++){
		int x,y;
		scanf("%d%d",&x,&y);
		add(x,y);add(y,x);
	}
	dfs1(root,0,1);
	dfs2(root,root);
	build(1,1,n);
	while(m--){
		int k,x,y,z;
		scanf("%d",&k);
		if(k == 1){
			scanf("%d%d%d",&x,&y,&z);
			change_range(x,y,z);
		}
		else if(k == 2){
			scanf("%d%d",&x,&y);
			printf("%d\n",ask_range(x,y));
		}
		else if(k == 3){
			scanf("%d%d",&x,&y);
			change_son(x,y);
		}
		else{
			int x;
			scanf("%d",&x);
			printf("%d\n",ask_son(x));
		}
	}
	return 0;
}