介紹
主席樹即可持久化線段樹,由 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;
}