【算法】樹鏈剖分

首先聲明:「剖」 讀 「pou」 !!!!!解剖的剖(pou)!!!!!
真不知道爲何他們都讀 「pao」……node

進入正題:

介紹:

樹路徑信息維護算法。ios

將一棵樹劃分紅若干條鏈,用數據結構去維護每條鏈,複雜度爲O(logN)。c++

其實本質是一些數據結構/算法在樹上的推廣算法

                                         —————— 360百科數據結構

大概作法:

常見的路徑剖分的方法是輕重樹鏈剖分(啓發式剖分)ui

將樹中的邊分爲:輕邊和重邊 ž定義size(X)爲以X爲根的子樹的節點個數。 ž令V爲U的兒子節點中size值最大的節點,那麼邊(U,V)被稱爲重邊,樹中重邊以外的邊被稱爲輕邊。spa

性質:ž輕邊(U,V),size(V)<=size(U)/2。 ž從根到某一點的路徑上,不超過O(logN)條輕邊,不超過O(logN)條重路徑。code

                                         ——————360百科blog

感謝360百科的友情支持(鼓掌)get

前置知識:

每一個節點都有一個重兒子,定義爲:每一個節點的最大子樹的根

輕兒子定義:除了重兒子其他都是輕兒子

重邊定義:重兒子與它爸爸連起來的邊

輕邊定義:除了重邊全是輕邊

重鏈定義:由重邊組成的鏈

輕鏈定義:由輕邊組成的鏈

給個圖:

我天這什麼玩意兒 號是我隨便編的

毫無疑問,2 是 1 的重兒子,4 是 2 的重兒子,9 是 3 的重兒子,

而 6 , 7 , 8 都能夠是 4 的重兒子(重兒子只能有一個,因此只要在 6,7,8 中隨便選一個就能夠了)。

劃分完大概是這個亞子:

其中紫色的邊爲重邊,綠色的點爲重兒子。

具體作法 && 算法分析:

分爲三個大塊:

  1. 大法師一(dfs1):用於求每一個點的父親節點,重兒子,該點深度, 該(子)樹大小 。

  2. 大法師二(dfs2):用於求每一個點的 dfs 序,dfs 序對應着哪一個點,該點所在鏈的頂端 。

  3. 宿命(sum):能夠用於樹的兩點之間求和。

  4. 嫦娥(change):能夠用於區間修改等等等等。

1. 大法師一(dfs1):

聲明一下:

ft[i] 爲 i 的父親;
son[i] 爲 i 的重兒子;
dep[i] 爲 i 的深度;
size[i] 爲 以 i 爲根的子樹的大小。

用代碼說:

void dfs1(int now, int fa, int de) { // now 爲如今所在的點,fa 爲 now 的父親,de 爲該點深度。
	int v;
	ft[now] = fa; // 記錄父親
	dep[now] = de; // 記錄深度
	size[now] = 1; // 每一個點初始大小爲 1 (由於包括它本身)
	for (int i = head[now]; i; i = tree[i].nxt) {
		v = tree[i].v;
		if (v != fa) { // 避免回溯回去
			dfs1(v, now, de + 1); // 搜索
			size[now] += size[v]; // 加上子樹的大小
			if (size[v] > size[son[now]]) son[now] = v; // 記錄重兒子
		}
	}
}

2. 大法師二(dfs2):

聲明一下:

dfn[i] 爲 i 的 dfs 序;
fdn[i] 爲 dfs 序爲 i 表明的節點;(不要在乎我起名的方式)
top[i] 爲 i 點所在鏈的頂端;

用代碼說:

void dfs2(int now, int t) { // now 爲當前節點,t 爲當前節點所在鏈的頂端。
	int v;
	top[now] = t; // 記錄頂端
	dfn[now] = ++cnt; // 記錄 dfs 序
	fdn[cnt] = now; // 記錄 dfs 序所表明的節點
	if (son[now]) dfs2(son[now], t); // 優先遍歷重兒子,使其組成一條鏈
	for (int i = head[now]; i; i = tree[i].nxt) {
		v = tree[i].v;
		if (v != ft[now] && v != son[now]) dfs2(v, v); // 而後遍歷輕兒子,輕兒子的頂端爲本身
	}
}

3. 宿命(sum):

以求和爲例:

首先咱們知道,

輕重鏈剖分實際上是在數據結構的基礎上實現的,只不過加上了一些求 LCA 的元素而已,

那麼,是哪個數據結構呢?

讓咱們掌聲有請:

線段樹!!!(嘔)

沒錯,就是la個一調調一天的 神(po)奇(lan)數(wan)據(yi)!!

沒法否定的是,線段樹確實是區間求和的很好的一種方式,考試騙分就靠它!(除了有點頭疼)

代碼來啦~~:

int sum(int x, int y) {
	int ans = 0;
	while (top[x] != top[y]) { // 若是兩點不在同一條鏈上
		if (dep[top[x]] < dep[top[y]]) swap(x, y); // 保證 x 點深度比 y 大,讓 x 點往上跳。
		L = dfn[top[x]], R = dfn[x];
		a_l(1);
		ans = (ans + ans_te) % p; // 加上這條鏈的區間值
		ans_te = 0; // 記得清零(或許有些人線段樹寫法跟我不同)
		x = ft[top[x]]; // 從這個鏈頂跳到它的父親
	}
	if (dep[x] > dep[y]) swap(x, y); // 保持從 x 向上跳
	L = dfn[x], R = dfn[y];
	a_l(1);
	ans = (ans + ans_te) % p;
	ans_te = 0;
	return ans % p; // 被 %p 支配的痛苦
}

4. 嫦娥(change):

上代碼:

void change(int x, int y, int z) { // 大概跟 sum 差很少,改一改就好了
	C = z;
	while (top[x] != top[y]) {
		if (dep[top[x]] < dep[top[y]]) swap(x, y);
		L = dfn[top[x]]; R = dfn[x];
		c_l(1);
		x = ft[top[x]];
	}
	if (dep[x] > dep[y]) swap(x, y);
	L = dfn[x], R = dfn[y];
	c_l(1);
}

完整代碼:(很少很少,只有 137 173 行)

#include <iostream>
#include <cstdio>
#include <queue>
#define int long long
#define MAXN 100010
#define LL long long
#define M(a, b) std :: make_pair(a, b)
#define F1(i, a, b) for (int i = a; i <= b; ++i)
#define F2(i, a, b) for (int i = a; i >= b; --i)
using namespace std;

int ft[MAXN], son[MAXN], dep[MAXN], size[MAXN];
int dfn[MAXN], fdn[MAXN], top[MAXN];
int head[MAXN], node[MAXN];
int cnt = 0, js = 0, n, m, r, p;
int ans_te = 0, L, R, C;

struct edge {
	int l, r, book, len;
	int sum;
}e[MAXN << 2];

struct Node {
	int v, nxt;
}tree[MAXN << 1];

void add(int u, int v) {
	tree[++js].v = v;
	tree[js].nxt = head[u];
	head[u] = js;
}

void buit(int l, int r, int k) {
	e[k].l = l;
	e[k].r = r;
	e[k].len = r - l + 1;
	if (l == r) {
		e[k].sum = (node[fdn[l]]) % p;
		return;
	}
	int mid = (l + r) >> 1;
	buit(l, mid, k << 1);
	buit(mid + 1, r, (k << 1) + 1);
	e[k].sum = (e[k << 1].sum + e[(k << 1) + 1].sum) % p;
}

void down(int k) {
	e[k << 1].book = (e[k << 1].book + (e[k].book) % p) % p;
	e[(k << 1) + 1].book = (e[(k << 1) + 1].book + (e[k].book) % p) % p;
	e[k << 1].sum = (e[k << 1].sum + (e[k].book * e[k << 1].len) % p) % p;
	e[(k << 1) + 1].sum = (e[(k << 1) + 1].sum + (e[k].book * e[(k << 1) + 1].len) % p) % p;
	e[k].book = 0;
}

void a_l(int k) {
	if (e[k].l >= L && e[k].r <= R) {
		ans_te = (ans_te + e[k].sum) % p;
		return;
	}
	if (e[k].book) down(k);
	int mid = (e[k].l + e[k].r) >> 1;
	if (L <= mid) a_l(k << 1);
	if (R > mid) a_l((k << 1) + 1);
}

void c_l(int k) {
	if (e[k].l >= L && e[k].r <= R) {
		e[k].sum = (e[k].sum + (C * e[k].len) % p) % p;
		e[k].book = (e[k].book + C) % p;
		return;
	}
	if (e[k].book) down(k);
	int mid = (e[k].l + e[k].r) >> 1;
	if (L <= mid) c_l(k << 1);
	if (R > mid) c_l((k << 1) + 1);
	e[k].sum = (e[k << 1].sum + e[(k << 1) + 1].sum) % p;
}

void dfs1(int now, int fa, int de) {
	int v;
	ft[now] = fa;
	dep[now] = de;
	size[now] = 1;
	for (int i = head[now]; i; i = tree[i].nxt) {
		v = tree[i].v;
		if (v != fa) {
			dfs1(v, now, de + 1);
			size[now] += size[v];
			if (size[v] > size[son[now]]) son[now] = v;
		}
	}
}

void dfs2(int now, int t) {
	int v;
	top[now] = t;
	dfn[now] = ++cnt;
	fdn[cnt] = now;
	if (son[now]) dfs2(son[now], t);
	for (int i = head[now]; i; i = tree[i].nxt) {
		v = tree[i].v;
		if (v != ft[now] && v != son[now]) dfs2(v, v);
	}
}

int sum(int x, int y) {
	int ans = 0;
	while (top[x] != top[y]) {
		if (dep[top[x]] < dep[top[y]]) swap(x, y);
		L = dfn[top[x]], R = dfn[x];
		a_l(1);
		ans = (ans + ans_te) % p;
		ans_te = 0;
		x = ft[top[x]];
	}
	if (dep[x] > dep[y]) swap(x, y);
	L = dfn[x], R = dfn[y];
	a_l(1);
	ans = (ans + ans_te) % p;
	ans_te = 0;
	return ans % p;
}

void change(int x, int y, int z) {
	C = z;
	while (top[x] != top[y]) {
		if (dep[top[x]] < dep[top[y]]) swap(x, y);
		L = dfn[top[x]]; R = dfn[x];
		c_l(1);
		x = ft[top[x]];
	}
	if (dep[x] > dep[y]) swap(x, y);
	L = dfn[x], R = dfn[y];
	c_l(1);
}

signed main() {
	int x, y, z, c;
	scanf("%lld %lld %lld %lld", &n, &m, &r, &p);
	F1(i, 1, n) scanf("%lld", &node[i]);
	F1(i, 1, n - 1) {
		scanf("%lld %lld", &x, &y);
		add(x, y); add(y, x);
	}
	dfs1(r, 0, 1);
	dfs2(r, r);
	buit(1, n, 1);
	F1(i, 1, m) {
		ans_te = 0;
		scanf("%lld", &c);
		if (c == 1) {
			scanf("%lld %lld %lld", &x, &y, &z);
			change(x, y, z);
		}
		else if (c == 2) {
			scanf("%lld %lld", &x, &y);
			printf("%lld\n", sum(x, y));
		}
		else if (c == 3) {
			scanf("%lld %lld", &x, &z);
			C = z; L = dfn[x]; R = dfn[x] + size[x] - 1;
			c_l(1);
		}
		else if (c == 4) {
			ans_te = 0;
			scanf("%lld", &x);
			L = dfn[x]; R = dfn[x] + size[x] - 1;
			a_l(1);
			printf("%lld\n", ans_te);
		}
	}
	return 0;
}

給兩個模板題:

  1. 洛谷 P3384 【模板】輕重鏈剖分

  2. 洛谷 GSS7 - Can you answer these queries VII

(壞笑)

相關文章
相關標籤/搜索