線段樹合併(四道例題)

顧名思義,就是合併兩個同構(就是維護的區間長度一樣)線段樹,其實也沒啥比較nb的算法,就是一個一個節點的合併,但是如果在n個要合併的線段樹裏,如果一共有m個元素,則配合動態開點,複雜度會均攤成一個驚人的O(mlogn)O(mlogn)所以,在多次合併的均攤複雜度是非常優秀的.另外線段樹合併還可以和線段樹分裂一起構成維護一組線段樹森林的方法
我們每次合併一個點,就是綜合兩個線段樹表示相同區間的兩個節點的信息,然後整合成一個,刪去另一個,這時,我們可以有一個垃圾回收處理,如下:

//bac數組就是垃圾桶數組,如果裏面有節點,就優先取出用掉,要是沒有就另起新點
inline int newnod() {return (cnt?bac[cnt--]:++tot);}
inline void del(int p) {bac[++cnt] = p; tr[p].l = tr[p].r = tr[p].val = 0;}

合併函數可以點點進行直接合並,如果這樣不方便,也可以只針對葉子節點進行直接合並,其他節點通過pushup操作得出.(總之是兩個線段樹所有節點都遍歷一邊)
例一: P4556 Vani有約會 雨天的尾巴 線段樹合併模板
如題是模板題,我們講z種不同的物資針對每個節點維護一個權值線段樹(即每個節點一個).然後按照樹上差分的思想,對於路徑(x,y)加上z物資一件,就讓x和y的權值線段樹z位置加一,lca(x,y)和fa(lca(x,y))的權值z位置減一,最後dfs一邊執行線段樹合併,就行啦.

#include <iostream>
#include <cstring>
#include <string>
#include <vector>
#include <queue>
#include <algorithm>
#include <cstdio>
#include <cmath>
#define ll long long
using namespace std;
const int N = 2e5 + 5;
const int Z = 1e5 + 2;
int n, m;
int he[N], ver[N], ne[N], tot;
int d[N];
queue<int> q;
int f[N][30];
inline void add(int x, int y)
{
	ver[++tot] = y;
	ne[tot] = he[x];
	he[x] = tot;
}
void bfs()
{
	d[1] = 1;
	q.push(1);
	while (q.size())
	{
		int te = q.front();
		q.pop();
		for (int i = he[te]; i; i = ne[i])
		{
			int v = ver[i];
			if (d[v]) continue;
			d[v] = d[te] + 1;
			f[v][0] = te;
			for (int j = 1; j < 30; j++)
				f[v][j] = f[f[v][j - 1]][j - 1];
			q.push(v);
		}
	}
}
int lca(int x, int y)
{
	if (d[x] > d[y]) swap(x, y);
	for (int i = 29; i >= 0; i--)
	{
		if (d[f[y][i]] < d[x]) continue;
		y = f[y][i];
	}
	if (x == y) return x;
	for (int i = 29; i >= 0; i--)
		if (f[x][i] != f[y][i]) x = f[x][i], y = f[y][i];
	return f[x][0];
}
struct Node
{
	int l, r;
	int val;
	int id;
}tr[N * 40];
int cnt, top;
int rt[N], bac[N*40], ans[N];
inline int newnod() { return cnt ? bac[cnt--] : ++tot; }
inline void del(int p) { bac[++cnt] = p; tr[p].l = tr[p].r = tr[p].val = 0; }
inline void pushup(int p)
{
	if (tr[tr[p].l].val >= tr[tr[p].r].val)
	{
		tr[p].val = tr[tr[p].l].val;
		tr[p].id = tr[tr[p].l].id;
	}
	else
	{
		tr[p].val = tr[tr[p].r].val;
		tr[p].id = tr[tr[p].r].id;
	}

}
void insert(int &p, int pos, int v, int l = 1, int r = Z)
{
	if (!p) p = newnod();
	if (l == r)
	{
		tr[p].val += v;
		tr[p].id = l;
		return;
	}
	int mid = (l + r) >> 1;
	if (pos <= mid) insert(tr[p].l, pos, v, l, mid);
	else insert(tr[p].r, pos, v, mid + 1, r);
	pushup(p);
}
int merge(int x, int y, int l = 1, int r = Z)
{
	if (!x || !y) return x + y;
	int mid = (l + r) >> 1;
	if (l == r)
		tr[x].val += tr[y].val, tr[x].id = l;
	else
	{
		tr[x].l = merge(tr[x].l, tr[y].l, l, mid);
		tr[x].r = merge(tr[x].r, tr[y].r, mid + 1, r);
		pushup(x);
	}
	del(y);
	return x;
}
void print(int p, int l = 1, int r = Z)
{
	cout << l << " " << r << " " << tr[p].val << " " << tr[p].id << endl;
	if (l == r) return;
	int mid = (l + r) >> 1;
	print(tr[p].l, l, mid);
	print(tr[p].r, mid + 1, r);
}
void dfs_mg(int cur, int fa)
{
	for (int i = he[cur]; i; i = ne[i])
	{
		int y = ver[i];
		if (y == fa) continue;
		dfs_mg(y, cur);
		rt[cur] = merge(rt[cur], rt[y]);
	}
	if (tr[rt[cur]].val)
		ans[cur] = tr[rt[cur]].id;
	return;
}
int main()
{
	cin >> n >> m;
	for (int i = 1; i < n; i++)
	{
		int x, y;
		scanf("%d%d", &x, &y);
		add(x, y); add(y, x);
	}
	bfs();
	while (m--)
	{
		int x, y, t;
		scanf("%d%d%d", &x, &y, &t);
		insert(rt[x], t, 1);
		insert(rt[y], t, 1);
		int _lca = lca(x, y);
		insert(rt[_lca], t, -1);
		insert(rt[f[_lca][0]], t, -1);
	}
	dfs_mg(1, 0);
	for (int i = 1; i <= n; i++)
		printf("%d\n", ans[i]);
	return 0;
}

例二: P1600 天天愛跑步
這個題寫了很長時間,想了半天,才從之前做的一個題裏收到啓發,觀察這個題,如果我們對每一個點都針對n秒維護一個權值線段樹,那麼,針對一個路路徑,線段樹向父節點合併就有兩種情況:

1.路徑是從子節點到父節點,這時,我們必須要讓子節點線段樹的所有元素都"整體向前移一位",即1秒的數量變成2秒的數量,2秒的數量變成3秒的數量…依次類推
2.路徑是從父節點到子節點,這時,我們必須要讓子節點線段樹的所有元素都"整體向後移一位",即2秒的數量變成1秒的數量,3秒的數量變成2秒的數量…依次類推

但是這種操作很難在極短時間內進行,這時我們不如建立一個整體值sansan,針對1情況,我們每上一層sansan值都加一,我們往線段樹中壓入的是形式值,而實際值爲sansan+形式值,比如,在一層某個節點的sansan值爲6,這時我們在這裏壓入一個0秒,我們修改該點的權值線段樹,但是不是讓0位置+1,而是讓0san=60-san=-6位置加一,因爲-6是這一層0的形式值.這時我們往上走兩層,這時sansan值等於8,此時我們查詢2秒的個數,這時我們其實是查2秒在這一層的形式值的個數,即2san=62-san=-6,這時我們在前插入的0秒,在這產生了影響,總而言之,我們在一個點插入形式值後,這個形式值,會依據整體值sansan的不同在各層產生不同影響.然後我們必須讓這課樹的每一層的sansan值統一,我們發現樹的深度是一個比較好的天然sansan值.於是乎,針對每個路徑我們拆成兩部分<x,lca><x,lca><lca,y><lca,y>分別依據差分思想插入樹中,然後按照不同的sansan值規則合併兩邊就ok啦!

insert(rt[x], idn(0-dep2(x)), 1);
insert(rt[f[_lca][0]], idn(0-dep2(x)), -1);//第一部分

tt = dep1[x] - dep1[_lca]) + (dep1[y] - dep1[_lca];//路徑長度
insert(rt[y], idn(tt-dep1[y]), 1);
insert(rt[_lca], idn(tt-dep1[y]), -1);//第二部分

ps.這可能是迄今爲止自己琢磨出的最震撼的算法了QAQ
下面是ac代碼:

#include <iostream>
#include <cstring>
#include <string>
#include <vector>
#include <queue>
#include <algorithm>
#include <cstdio>
#include <cmath>
#define ll long long
#define max(x, y) ((x)>(y)?(x):(y))
using namespace std;
const int N = 6e5 + 5;
const int Z = 6e5 + 5;
int n, m;
int he[N], ver[N], ne[N], tot;
int dep1[N], deep;
int d[N];
int q[N], qh, ql;
int f[N][30];
inline void add(int x, int y)
{
	ver[++tot] = y;
	ne[tot] = he[x];
	he[x] = tot;
}
inline int idn(int n) {return n + 3e5+5;}
inline int dep2(int n) {return deep - dep1[n];}
void bfs()
{
	d[1] = 1;
	q[++qh] = 1;
	while (qh > ql)
	{
		int te = q[++ql];
		for (int i = he[te]; i; i = ne[i])
		{
			int v = ver[i];
			if (d[v]) continue;
			d[v] = d[te] + 1;
			f[v][0] = te;
			for (int j = 1; j < 30; j++)
				f[v][j] = f[f[v][j - 1]][j - 1];
			dep1[v] = dep1[te] + 1;
			deep = max(dep1[v], deep);
			q[++qh] = v;
		}
	}
}
int lca(int x, int y)
{
	if (d[x] > d[y]) swap(x, y);
	for (int i = 29; i >= 0; i--)
	{
		if (d[f[y][i]] < d[x]) continue;
		y = f[y][i];
	}
	if (x == y) return x;
	for (int i = 29; i >= 0; i--)
		if (f[x][i] != f[y][i]) x = f[x][i], y = f[y][i];
	return f[x][0];
}
struct Node
{
	int l, r;
	int val;
}tr[N * 40];
int cnt, top;
int rt[N], bac[N*40];
inline int newnod() { return cnt ? bac[cnt--] : ++top; }
inline void del(int p) { bac[++cnt] = p; tr[p].l = tr[p].r = tr[p].val = 0; }
void insert(int &p, int pos, int v, int l = 1, int r = Z)
{
	if (!p) p = newnod();
	tr[p].val += v;
	if (l == r) return;
	int mid = (l + r) >> 1;
	if (pos <= mid) insert(tr[p].l, pos, v, l, mid);
	else insert(tr[p].r, pos, v, mid + 1, r);
}
int merge(int x, int y, int l = 1, int r = Z)
{
	if (!x || !y) return x + y;
	tr[x].val += tr[y].val;
	int mid = (l + r) >> 1;
	tr[x].l = merge(tr[x].l, tr[y].l, l, mid);
	tr[x].r = merge(tr[x].r, tr[y].r, mid + 1, r);
	del(y);
	return x;
}
int qt[N], ans[N];
bool flag = 0;
int ask(int p, int k, int l = 1, int r = Z)
{
	if (l == r) return tr[p].val;
	int mid = (l + r) >> 1;
	if (k <= mid) return ask(tr[p].l, k, l, mid);
	else return ask(tr[p].r, k, mid+1, r);
}
void dfs_mg(int cur, int fa)
{
	for (int i = he[cur]; i; i = ne[i])
	{
		int y = ver[i];
		if (y == fa) continue;
		dfs_mg(y, cur);
		rt[cur] = merge(rt[cur], rt[y]);
	}
	int tt = qt[cur];
	ans[cur] += ask(rt[cur], idn(tt - (flag ? dep1[cur] : dep2(cur))));
	return;
}
struct qy
{
	int lca, y;
	int lca_m, y_m;
}qr[N];
int main()
{
	cin >> n >> m;
	for (int i = 1; i < n; i++)
	{
		int x, y;
		scanf("%d%d", &x, &y);
		add(x, y); add(y, x);
	}
	bfs();
	for (int i = 1; i <= n; i++) scanf("%d", &qt[i]);
	for (int i = 1; i <= m; i++)
	{
		int x, y;
		scanf("%d%d", &x, &y);
		int _lca = lca(x, y);
		insert(rt[x], idn(0-dep2(x)), 1);
		insert(rt[f[_lca][0]], idn(0-dep2(x)), -1);
		qr[i].lca = _lca; qr[i].y = y; qr[i].y_m = (dep1[x] - dep1[_lca]) + (dep1[y] - dep1[_lca]);
	}
	dfs_mg(1, 0);
	cnt = top = 0;
	for (int i = 0; i < N * 40; i++) tr[i].l = tr[i].r = tr[i].val = 0;
	memset(rt, 0, sizeof(rt));
	for (int i = 1; i <= m; i++)
	{
		int y = qr[i].y, _lca = qr[i].lca;
		int tt = qr[i].y_m;
		insert(rt[y], idn(tt-dep1[y]), 1);
		insert(rt[_lca], idn(tt-dep1[y]), -1);
	}
	flag = 1;
	dfs_mg(1, 0);
	for (int i = 1; i <= n; i++)
		printf("%d ", ans[i]);
	puts("");
	return 0;
}

例三:P3224 [HNOI2012]永無鄉
一個模板中的模板了.結合並查集找根即可.
下面是ac代碼:

#include <iostream>
#include <cstring>
#include <string>
#include <vector>
#include <queue>
#include <algorithm>
#include <cstdio>
#include <cmath>
#define ll long long
using namespace std;
const int N = 2e5 + 5;
int fa[N];
int fi(int x)
{
    if (x == fa[x]) return x;
    return fa[x] = fi(fa[x]);
}
struct Node
{
	int l, r;
	int val;
}tr[N * 40];
int cnt, top;
int rt[N], bac[N*40];
inline int newnod() { return cnt ? bac[cnt--] : ++top; }
inline void del(int p) { bac[++cnt] = p; tr[p].l = tr[p].r = tr[p].val = 0; }
int n, m;
void insert(int &p, int pos, int v, int l = 1, int r = n)
{
	if (!p) p = newnod();
	tr[p].val += v;
	if (l == r) return;
	int mid = (l + r) >> 1;
	if (pos <= mid) insert(tr[p].l, pos, v, l, mid);
	else insert(tr[p].r, pos, v, mid + 1, r);
}
int merge(int x, int y, int l = 1, int r = n)
{
	if (!x || !y) return x + y;
	int mid = (l + r) >> 1;
	tr[x].val += tr[y].val;
	tr[x].l = merge(tr[x].l, tr[y].l, l, mid);
	tr[x].r = merge(tr[x].r, tr[y].r, mid + 1, r);
	del(y);
	return x;
}
void mmerge(int x, int y)
{
    fa[y] = x;
    rt[x] = merge(rt[x], rt[y]);
}
int ask(int p, int k,  int l = 1, int r = n)
{
	if (l == r) return l;
	int mid = (l + r) >> 1;
	if (tr[tr[p].l].val >= k) return ask(tr[p].l, k, l, mid);
	else return ask(tr[p].r, k - tr[tr[p].l].val, mid+1, r);
}
void Debug_print(int p, int l = 1, int r = n)
{
	cout << l << " " << r << " " << tr[p].val << endl;
	if (l == r) { return;}
	int mid = (l + r) >> 1;
	Debug_print(tr[p].l, l,  mid);
	Debug_print(tr[p].r, mid + 1, r);
}
void De()
{
	for (int i = 1; i <= n; i++)
		cout << fa[i] << " ";
	cout << endl;
	for (int i = 1; i <= n; i++)
	{
		cout << "--------------" << endl;
		cout << i << endl;		
		for (int j = 1; j <= n; j++)
			Debug_print(rt[i], j);
		cout << "\n--------------" << endl;
	}
}
int _rank[N];
int main()
{
	cin >> n >> m;
	for (int i = 1; i <= n; i++)
	{
		fa[i] = i;
		int te; scanf("%d", &te);
		insert(rt[i], te, 1);
		_rank[te] = i;
	}
	while(m--)
	{
		int x, y;
		scanf("%d%d", &x, &y);
		x = fi(x); y = fi(y);
		if (x != y) mmerge(x, y);
	}
	int q; cin >> q;
	while(q--)
	{
		char op[5];
		int x, y;
		scanf("%s%d%d", op, &x, &y);
		if (op[0] == 'Q')
		{
			x = fi(x);
			if (tr[rt[x]].val < y) {puts("-1"); continue;}
			int te = ask(rt[x], y);
			printf("%d\n", _rank[te]);
			
		}
		else
		{
			x = fi(x); y = fi(y);
			if (x != y) mmerge(x, y);
		}
		
	}
	return 0;
}

補充一例:CF600E Lomsat gelral
這個題可以用dsu來做,但是今天發現可以用線段樹合併也可以!啊啊啊,這些神奇的算法還是這麼的神奇,個人覺得線段樹合併比dsu還要好理解一點.
下面是ac代碼:

#include <iostream>
#include <algorithm>
#include <cmath>
#define ll long long 
#define int ll
using namespace std;
const int N = 1e5+5;
int top, cnt, tot;
int bac[N*40], ver[N<<1], he[N], ne[N<<1], rt[N];
int su[N];
int n;
struct Node
{
	int l, r; ll val, ans;
}tr[N*40];
inline int neww() { return cnt ? bac[cnt--] : ++top; }
inline void del(int p) { bac[++cnt]; tr[p].l = tr[p].r = tr[p].val = 0; } 
void add(int x, int y)
{
	ver[++tot] = y;
	ne[tot] = he[x];
	he[x] = tot;
}
inline void pushup(int p)
{
	if (tr[tr[p].l].val > tr[tr[p].r].val)
	{
		tr[p].val = tr[tr[p].l].val;
		tr[p].ans = tr[tr[p].l].ans;
	}
	else if (tr[tr[p].l].val < tr[tr[p].r].val)
	{
		tr[p].val = tr[tr[p].r].val;
		tr[p].ans = tr[tr[p].r].ans;
	}
	else
	{
		tr[p].val = tr[tr[p].l].val;
		tr[p].ans = tr[tr[p].l].ans + tr[tr[p].r].ans;
	}
	
}
void ins(int &p, int k, int val, int l = 1, int r = n)
{
	if (!p) p = neww();
	if (l == r){tr[p].val += val; tr[p].ans = l; return;}
	int mid = (l + r) >> 1;
	if (k <= mid) ins(tr[p].l, k, val, l, mid);
	else ins(tr[p].r, k, val, mid+1, r);
	pushup(p);
}
int merge(int x, int y, int l = 1, int r = n)
{
	if (!x || !y) return x + y;
	if (l == r) 
	{tr[x].val += tr[y].val; tr[x].ans = l;}
	else
	{
		int mid = (l + r) >> 1;
		tr[x].l = merge(tr[x].l, tr[y].l, l, mid);
		tr[x].r = merge(tr[x].r, tr[y].r, mid+1, r);
		pushup(x);
	}
	del(y);
	return x;
}
int ans[N];
void dfs_mg(int cur, int fa)
{
	for (int i = he[cur]; i; i = ne[i])
	{
		int y = ver[i];
		if (y == fa) continue;
		dfs_mg(y, cur);
		rt[cur] = merge(rt[cur], rt[y]);
	}
	ans[cur] = tr[rt[cur]].ans;
	return;
}
signed main()
{
	cin >> n;
	for (int i = 1; i <= n; i++) 
	{
		scanf("%lld", &su[i]);
		ins(rt[i], su[i], 1);
	}
	for (int i = 1; i < n; i++)
	{
		int x, y; scanf("%lld%lld", &x, &y);
		add(x, y); add(y, x);
	}
	dfs_mg(1, 0);
	for (int i = 1; i <= n; i++)
	printf("%lld ", ans[i]);
	puts("");
	return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章