【學習筆記】主席樹

概述

就是可持久化的線段樹。更直白一點,就是 很多個線段樹套在一起(共用信息相同的節點)。

幹嘛

可以解決區間第kk大等神奇問題。

優點

快。這不是廢話嗎

普通版の實現

0x01初始化
一開始,我們需要一顆線段樹。就按照普通的建樹方法即可。
0x02建新樹

  • 要素是 不改變原有的節點。因爲本質是很多不同的線段樹,只是用一些奇技淫巧來省空間(和時間)而已。
  • 首先,將原來的樹的 對應節點複製到當前節點。包括子節點是誰。也就是說,現在這兩個點 共用了相同的子節點
  • 然後考慮子節點——如果有修改,則 申請新節點,遞歸的建樹。否則不動。

在理解這個玩意兒的時候,你就這麼想:其實兩個點的子節點不同,沒有共用子節點,只是子節點住一間屋子而已。反正我們也不會把房子拆了,即修改原有的節點。
在這裏插入圖片描述
也就是說,本質是這樣的兩顆線段樹。綠色的編號表示 內存地址。共用內存罷了。
——前提條件是這兩個節點 信息相同
這樣做的好處,在 單點修改 時很明顯:只需要新建logn\log n個點。因爲只有logn\log n這一條單鏈上的點(即紅色的點)與原有的節點信息不同。
或者放點代碼?

void modify(int old,int &o,int id){
	o = cntNode ++; // 申請新節點 
	node[o] = node[old]; // 複製舊節點 
	if(node[old].l == node[old].r){ // 葉子節點,直接更改信息
		++ node[o].ppl;
		return ;
	}
	if(id <= (node[o].l+node[o].r)>>1) // 說明右子樹與原有的節點信息是相同的
		modify(node[old].lson,node[o].lson,id);
	else // 遞歸修改子樹
		modify(node[old].rson,node[o].rson,id);
	pushUp(o); // 更新當前節點信息
}
  • 但是,就像普通的線段樹,任何查詢操作都需要 知道根是誰。所以你得用一個root\text{root}數組把第xx個線段樹的根存下來。

順便一說,區間修改 就必須使用奇技淫巧了。

  • 標記永久化。就是 捨棄pushdown\text{pushdown},遞歸時自己惦記着
  • 下傳也開新點。爲了不修改原有節點嘛。

0x03查詢
當成普通線段樹。——別忘了 人類的本質 主席樹的本質就是 真香! 普通線段樹的省空間版本。

動態開點の線段樹

0x01思想
權值線段樹在權值很大的時候得離散化。很蛋疼的 於是我們繼續用 共用內存 的思想——
任意兩個從未修改過的點,信息(左右端點除外)一定相同!(至少在大多數權值線段樹中如此)。
所以我們可以把它們全部搞到一個專門存“虛點”的內存裏。比如00號點。
0x02初始化
我們一開始 不需要建好樹——因爲整棵樹都是“虛點”。直接申請一個虛點作爲根,還順便搞到了“虛點”的地址呢!
0x03建新樹
同正常版。雖說叫做“虛點”,實際上還是一種共用內存的思想。但是這樣的共用內存 不純粹。因爲左右端點並不相同。好消息:我們用不到左右端點!因爲“虛點”是無需訪問子節點的;要訪問,就代表有修改,就會變成“實點”。
懶得話在函數傳參時傳進去也沒啥

例題

0x01板題
如題

// 離散化,正常版
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;

const int MaxN = 200005;
struct segmentTreeNode{ // 權值線段樹
	int lson, rson, l, r;
	int ppl; // PPL orz or2 orz
}node[MaxN<<5];
int root[MaxN], cntNode;
void pushUp(int pos){
	node[pos].ppl = node[node[pos].lson].ppl+node[node[pos].rson].ppl;
}
void build(int &o,int l,int r){
	o = cntNode ++;
	node[o].l = l; node[o].r = r;
	if(l != r){
		build(node[o].lson,l,(l+r)>>1);
		build(node[o].rson,(l+r)/2+1,r);
		pushUp(o);
	}
	else{ // is a leaf
		node[o].ppl = 0;
	}
}
void modify(int old,int &o,int id){
	o = cntNode ++;
	node[o] = node[old];
	if(node[old].l == node[old].r){
		++ node[o].ppl;
		return ;
	}
	if(id <= (node[o].l+node[o].r)>>1)
		modify(node[old].lson,node[o].lson,id);
	else
		modify(node[old].rson,node[o].rson,id);
	pushUp(o);
}
int query(int old,int o,int k){
	if(node[o].l == node[o].r)
		return node[o].l;
	if(node[node[o].lson].ppl-node[node[old].lson].ppl >= k)
		return query(node[old].lson,node[o].lson,k);
	k -= (node[node[o].lson].ppl-node[node[old].lson].ppl);
	return query(node[old].rson,node[o].rson,k);
}

int lsh[MaxN], a[MaxN], n;
int zxyMeier[MaxN];
void getLSH(){
	for(int i=0; i<n; ++i)
		zxyMeier[i] = a[i];
	sort(zxyMeier,zxyMeier+n);
	for(int i=0; i<n; ++i)
		lsh[i] = lower_bound(zxyMeier,zxyMeier+n,a[i])-zxyMeier;
}
int main(){
	int q;
	scanf("%d %d",&n,&q);
	for(int i=0; i<n; ++i)
		scanf("%d",&a[i]);
	getLSH();
	build(root[0],1,n);
	for(int i=1; i<=n; ++i)
		modify(root[i-1],root[i],lsh[i-1]+1);
	for(int i=1,l,r,k; i<=q; ++i){
		scanf("%d %d %d",&l,&r,&k);
		printf("%d\n",zxyMeier[query(root[l-1],root[r],k)-1]);
	}
	return 0;
}
// ---------------密封線---------------
//  請  勿  在  密  封  線  內  答  題
// 動態開點版,非常牛(sao)逼(qi)
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;

const int MaxN = 200005, infty = 1e9;
struct segmentTreeNode{
	int lson, rson;
	int ppl; // PPL orz or2 orz
	segmentTreeNode(){
		ppl = lson = rson = 0;
	}
}node[MaxN<<5];
int root[MaxN], cntNode;
int newNode(){ // 動態開點! 
	return cntNode ++;
} // 儘管這個函數看着挺傻的……
# define mid ((l+r-1)>>1) // 用來處理負數域
void pushUp(int pos){
	node[pos].ppl = node[node[pos].lson].ppl+node[node[pos].rson].ppl;
}
void modify(int old,int &o,int l,int r,int id){
	o = newNode();
	node[o] = node[old];
	if(l == r){ // is a leaf
		++ node[o].ppl;
		return ;
	}
	if(id <= mid)
		modify(node[old].lson,node[o].lson,l,mid,id);
	else
		modify(node[old].rson,node[o].rson,mid+1,r,id);
	pushUp(o);
}
int query(int old,int o,int l,int r,int k){
	if(l == r) return l; // a leaf
	if(node[node[o].lson].ppl-node[node[old].lson].ppl >= k)
		return query(node[old].lson,node[o].lson,l,mid,k);
	k -= (node[node[o].lson].ppl-node[node[old].lson].ppl);
	return query(node[old].rson,node[o].rson,mid+1,r,k);
}

int a[MaxN], n;
int main(){
	int q;
	scanf("%d %d",&n,&q);
	for(int i=1; i<=n; ++i)
		scanf("%d",&a[i]);
	root[0] = newNode(); // 創建“虛點” 
	for(int i=1; i<=n; ++i)
		modify(root[i-1],root[i],-infty,infty,a[i]);
	for(int l,r,k; q; --q){
		scanf("%d %d %d",&l,&r,&k);
		printf("%d\n",query(root[l-1],root[r],-infty,infty,k));
	}
	return 0;
}

0x02板題二號
題目描述
(閱讀程序,猜測題目)
提示:很板!(所以不想打題目描述)

#include <cstdio>
#include <iostream>
#include <vector>
using namespace std;

const int MaxN = 100005;
struct segmentTreeNode{
	int lson, rson, l, r;
	int maxV;
}node[MaxN<<3];
int root[MaxN], cntNode;
int val[MaxN];
void pushUp(int pos){
	node[pos].maxV = max(node[node[pos].lson].maxV,node[node[pos].rson].maxV);
}
void build(int &o,int l,int r){
	o = cntNode ++;
	node[o].l = l; node[o].r = r;
	if(l != r){
		build(node[o].lson,l,(l+r)>>1);
		build(node[o].rson,(l+r)/2+1,r);
		pushUp(o);
	}
	else
		node[o].maxV = val[l];
}
void modify(int old,int &o,int id,int v){
	o = cntNode ++;
	node[o] = node[old];
	if(node[old].l == node[old].r){
		node[o].maxV = v;
		return ;
	}
	if(id <= (node[old].l+node[old].r)>>1)
		modify(node[old].lson,node[o].lson,id,v);
	else
		modify(node[old].rson,node[o].rson,id,v);
	pushUp(o);
}
int query(int o,int l,int r){
	if(l <= node[o].l and node[o].r <= r)
		return node[o].maxV;
	if(r <= (node[o].l+node[o].r)>>1)
		return query(node[o].lson,l,r);
	if(l > (node[o].l+node[o].r)>>1)
		return query(node[o].rson,l,r);
	return max(query(node[o].lson,l,r),query(node[o].rson,l,r));
}

int main(){
	int n, q;
	scanf("%d %d",&n,&q);
	for(int i=1; i<=n; ++i)
		scanf("%d",&val[i]);
	build(root[1],1,n);
	int edition = 1;
	while(q --){
		int cmd, k, one, two;
		scanf("%d %d %d %d",&cmd,&k,&one,&two);
		if(cmd == 0)
			printf("%d\n",query(root[k],one,two));
		else
			modify(root[k],root[++ edition],one,two);
	}
	return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章