演算法隨筆——主席樹(可持久化線段樹)

codwarm發表於2024-05-04

介紹

主席樹即可持久化線段樹,由 hjt 大佬發明。
好像又稱函式式線段樹。
可以查詢序列在任意時間的歷史狀態。
學習連結 學習連結

主要思路

維護歷史狀態,若採用直接複製整棵樹的方式,空間爆炸。
可以發現每次修改只有部分節點發生了改變(其實就是到樹根那條鏈會改變),因此每次只需要記錄下來這條鏈的變化即可。具體來說,相比複製整棵樹,最佳化的地方是將新樹與舊樹沒有修改的部分合並,因此只新增加了一條鏈,空間複雜度 \(O(n \log n)\)

例題一

P3834 【模板】可持久化線段樹 2

思路

可持久化權值線段樹,用主席樹維護即可。

程式碼

#include <bits/stdc++.h>
using namespace std;

const int N = 2e5+5;

int n,m,a[N],root[N];
struct node
{
    int l,r,sum;
}t[N<<5]; //主席樹需要開32倍空間
int tot;

int insert(int k,int l,int r,int x,int v)
{
    int p = ++tot;
    t[p] = t[k]; //複製原樹
    if (l == r) {t[p].sum += v;return p;}
    int mid = l + r >> 1;
    if (x <= mid) t[p].l = insert(t[k].l,l,mid,x,v); //更新鏈
    else t[p].r = insert(t[k].r,mid + 1,r,x ,v);
    t[p].sum = t[t[p].l].sum + t[t[p].r].sum; //pushup
    return p;
}
int query(int p,int q,int l,int r,int x)
{
    if (l == r) return l;
    int mid = l + r >> 1;
    int cnt = t[t[p].l].sum-t[t[q].l].sum ; // 值域 l ~ r 中數的個數 
    if (x <= cnt) return query(t[p].l,t[q].l,l,mid,x);
    else return query(t[p].r,t[q].r,mid + 1,r,x-cnt);
}
int main()
{
    cin >> n >> m;
    vector<int> v;
    for (int i = 1;i <= n;i++) cin >> a[i],v.push_back(a[i]);
    //離散化
    sort(v.begin(),v.end());v.erase(unique(v.begin(),v.end()),v.end());
    for (int i= 1; i<= n;i++) a[i] = lower_bound(v.begin(),v.end(),a[i]) - v.begin() + 1;
    const int T = v.size() + 10; //值域範圍
    //root[i] 指 第 i 個版本的根
    for (int i = 1;i <= n;i++) root[i] = insert(root[i-1],1,T,a[i],1);
    for (int i = 1;i  <= m;i++) 
    {
        int l,r,k;cin >> l >>r >> k;
        //第 i 個版本儲存了 1~i 的數的貢獻
        //因為主席樹滿足可減性
        //因此用第 r 個版本 減去 第 l-1 個版本 可以得到 l~r的貢獻
        cout << v[query(root[r],root[l-1],1,T,k)-1] << endl;
    }
    return 0;
}

例題二

【模板】可持久化線段樹 1(可持久化陣列)
模板題。

思路

主席樹維護陣列的模板,需要支援修改和查詢歷史版本的陣列。

// Problem: P3919 【模板】可持久化線段樹 1(可持久化陣列)
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P3919
// Memory Limit: 1024 MB
// Time Limit: 1500 ms
// Author: Eason
// Date:2024-03-26 21:44:45
// 
// Powered by CP Editor (https://cpeditor.org)

#include<bits/stdc++.h>
using namespace std;
#define ull unsigned long long
#define ll long long
#define INF 0x3f3f3f3f
#define re register
#define il inline
#define gc getchar
inline int read()
{
	int f=1,k=0;
	char c = getchar();
	while (c <'0' || c > '9')
	{
		if (c=='-') f=-1;
		c=getchar();
	}
	while(c >= '0' && c <= '9')  k = (k << 1)+(k << 3)+(c^48),c=getchar();
	return k*f;
}

const int N = 1e6+5;

int a[N],n,m,root[N];

struct node
{
	int l,r,val;
}t[N << 5];
int tot;
int build(int l,int r) //建樹
{
	int p = ++tot;
	if (l == r) {t[p].val = a[l];return p;}
	int mid = l + r >> 1;
	t[p].l = build(l,mid);
	t[p].r = build(mid + 1,r);
	return p; 
}

int insert(int k,int l,int r,int x,int v)
{
	int p = ++tot;
	t[p] = t[k]; //複製原節點
	if (l == r) // 即沒有兒子
	{
		t[p].val = v;//直接賦值
		return p;
	}
	int mid =  l + r >> 1;
	if (x <= mid) t[p].l = insert(t[k].l,l,mid,x,v);
	else t[p].r = insert(t[k].r,mid + 1,r,x,v);
	return p;
}

int query(int k,int l,int r,int x) //查詢
{
	if (x < l || x > r) return 0;
	if (l == r) return t[k].val;
	int mid = l + r >> 1;
	return query(t[k].l,l,mid,x) + query (t[k].r,mid + 1,r,x);
}

int main()
{
	cin >> n >> m;
	for (int i = 1;i <= n;i++) a[i] = read();
	root[0] = build(1,n);
	for (int i = 1;i <= m;i++)
	{
		int x = read(),op = read(),y = read();
		if (op == 1)
		{
			int z = read();
			root[i] = insert(root[x],1,n,y,z);
		}
		else
		{
			printf("%d\n",query(root[x],1,n,y));
			root[i] = root[x];
		}
	}
	return 0;
}

相關文章