1. 程式人生 > >主席樹部分小總結

主席樹部分小總結

主席樹各部分,各個類別講解

註釋: 本篇只對對於主席樹有“足夠”認識的人瀏覽,對於主席樹的原理,思想,本人因為是複習筆記,一概不談。

作用類講解

  1. 像你谷這道題數顏色所用的就是主席樹的動態修改和查詢(線段樹版的)所以可以logn查詢很快。。
  2. 而像bzoj的K-th number 和洛谷的這道P3567 [POI2014]KUR-Couriers個人認為裡面有用到差分的思想。。就是運用主席樹動態開點來存取各種狀態

大體就像這樣吧,當然還可以和dfs序等等結合起來。。 所以可以用主席樹解決不同狀態下,不同區間中,一些狀態重複特別多的問題(個人認為傾向於這個方面)

建樹QAQ

俗話說:巧婦難為無米之炊

所以建樹這一步不可省。。但是網上在這一步,分出了涇渭分明的兩種方式

第一種

是先build(即建空樹),建立好模板,好讓後面的往上套。。。

一般為這樣

int build(int l,int r){
	int now=++cnt; sum[now]=0;
	if (l<r) {
		L[now]=build(l,mid);
		R[now]=build(mid+1,r);
	}
	return now;
}

可以看到,在build的過程中,除了動態開點,並沒有帶入一個值,,至於為神馬要這麼做都不知道的人,, 求百度搜索關鍵字"主席樹"。。

建立完模板就要往裡面套。。

int change
(int l,int r,int las,int t){ int now=++cnt; sum[now]=sum[las]+1; L[now]=L[las]; R[now]=R[las]; if (l<r){ if (t<=mid) L[now]=change(l,mid,L[las],t); else R[now]=change(mid+1,r,R[las],t); } return now; }

早期的馬蜂比較清奇。。勿噴。。 第一行,,正常的動態開下新樹,然後繼承(初始化的時候是往空樹裡放) 接下來是判定,,是否建左子樹或右子樹。

和線段樹合併真的到這裡幾乎一毛一樣,,所以我推薦這兩個東西一起復習。。。

這裡比較重要的就是

) L[now]=change(l,mid,L[las],t); else R[now]=change(mid+1,r,R[las],t);

這裡一定要有賦值不然輸出0000000000,無數個零,多少次輸出,多少個0;

第二種

經常被使用,個人覺得速度也不錯的一種

void update(int &now,int l,int r,int pla,int c){
	if (pla==0) return;if (!now) now=++cnt; 
	tree[now].num+=c;
	if (l==r) return;
	if (pla<=mid) update(tree[now].lc,l,mid,pla,c);else update(tree[now].rc,mid+1,r,pla,c);}

沒有build初始化而是在需要開點的時候就等於++cnt,一切都ok了。。 下面的操作的話,和第一種是一樣的,,誒,你說為什麼不要繼承左右子樹,,因為&的緣故,他在向下 搜的時候,如果是零就是初始狀態的話,下一層直接變成一顆新樹。。。

所以這裡有一個與線段樹差距最大的地方就是

線段樹的父親與左右子樹是now,now<<1,now<<1|1,的關係 而這裡。。。誰知道,,隨他吧。。

搜樹QWQ

建好了樹,不能不用啊。。

這裡要分類別了,,一種就是動態區間修改和查詢類的


int search(int root,int l,int r,int x,int y){
    if (x<=l&&y>=r) return tree[root].num;
    long long ans=0;
    if (x<=mid)  ans+=search(tree[root].lc,l,mid,x,y);
    if (y>mid) ans+=search(tree[root].rc,mid+1,r,x,y); return ans; }

還有一種是差分求解類的

int search(int now,int root,int l,int r,int pla){
    if(l==r) return l;
    if (tree[tree[now].lc].num-tree[tree[root].lc].num>pla) return search(tree[now].lc,tree[root].lc,l,mid,pla);
    else if (tree[tree[now].rc].num-tree[tree[root].rc].num>pla) return search(tree[now].rc,tree[root].rc,mid+1,r,pla);
    return 0;}

壓行稍微有點嚴重哈,,,

都是常用的模板,都是比較熟悉的 一般第一種和線段樹查詢幾乎一毛一樣,,只是在樹的轉移時,一個是<<1轉移,一個是通過陣列指標轉移,大體是一樣的啦。。

第二種就區別很大了,,這是運用主席樹的可持久化特點,,所以 (溜,不想講了) 大體就是二分思想,答案滿足單調性。。左子樹個數大於你要找的,那就都往左子樹中去找,(一定要前後狀態比較找,且要同一類子樹比較)小於左子樹,然後右子樹個數滿足的話,就往右子樹裡找,不然(特判)不同題目,不同對待。。。

書寫答案,請把你的答案工工整整的用cout/printf/write()/io.write 輸出來吧

下面貼程式碼

  1. 洛谷主席樹模板絕對是複習第一站的好題;
#include<bits/stdc++.h>
#define mid (l+r)/2
using namespace std;
int a[200010],b[200010];
int n,m,q,cnt;
int tree[5000010];
int sum[5000010];
int L[5000010];
int R[5000010];
int build(int l,int r){
   int now=++cnt; sum[now]=0;
   if (l<r) {
   	L[now]=build(l,mid);
   	R[now]=build(mid+1,r);
   }
   return now;
}
int change(int l,int r,int las,int t){
   int now=++cnt; sum[now]=sum[las]+1;
   L[now]=L[las]; R[now]=R[las]; 
   if (l<r){
   	if (t<=mid) L[now]=change(l,mid,L[las],t);
   	else R[now]=change(mid+1,r,R[las],t);
   }
   return now;
}
int find(int l,int r,int x1,int x2,int t){
   if (l>=r) return l;
   int x=sum[L[x2]]-sum[L[x1]];
   if (x>=t) return find(l,mid,L[x1],L[x2],t);
   else return find(mid+1,r,R[x1],R[x2],t-x);
}
int main(){
   cin>>n>>q;
   for (int i=1; i<=n;++i){
   	cin>>b[i],a[i]=b[i];
   }
   sort(a+1,a+1+n); cnt=0;
   m=unique(a+1,a+1+n)-a-1;
   tree[0]=build(1,m);
   for (int i=1; i<=n; ++i){
   	int t=lower_bound(a+1,a+1+m,b[i])-a;
   	tree[i]=change(1,m,tree[i-1],t);
   }
   for (int i=1; i<=q; ++i){
   	int x,y,z; 
   	cin>>x>>y>>z;
   	int ans=find(1,m,tree[x-1],tree[y],z);
   	cout<<a[ans]<<endl;
   }
}
  1. 數顏色 線段樹查詢類。。。難度較小
#include<bits/stdc++.h>
#define REP(i,a,b) for(int i(a);i<=(b); ++i)
#define mid ((l+r)>>1)
using namespace std;
struct node{
	int lc,rc,num;}tree[10000100];
int n,m,cnt,rt[1000010],a[1000010];
void push_up(int x){
	tree[x].num=tree[tree[x].lc].num+tree[tree[x].rc].num;}
void update(int &now,int l,int r,int pla,int c){
//	cout<<now<<endl;
	if (pla==0) return;if (!now) now=++cnt; 
	tree[now].num+=c;
	if (l==r) return;
	if (pla<=mid) update(tree[now].lc,l,mid,pla,c);else update(tree[now].rc,mid+1,r,pla,c);}
int search(int root,int l,int r,int x,int y){
	if (x<=l&&y>=r) return tree[root].num;
	long long ans=0;
	if (x<=mid)  ans+=search(tree[root].lc,l,mid,x,y);
	if (y>mid) ans+=search(tree[root].rc,mid+1,r,x,y); return ans; }

int main(){
	scanf("%d %d",&n,&m); cnt=0;
	REP(i,1,n) cin>>a[i],update(rt[a[i]],1,n,i,1); 
//	for(int i=1; i<=30; ++i) cout<<tree[i].num<<endl;
	REP(i,1,m) {
		int x,y,z,e; scanf("%d",&x); if (x==1) {scanf("%d%d%d",&y,&z,&e); printf("%d\n",search(rt[e],1,n,y,z));}
		if (x==2) { scanf("%d",&y); update(rt[a[y]],1,n,y,-1); update(rt[a[y+1]],1,n,y,1); 
		update(rt[a[y]],1,n,y+1,1);  update(rt[a[y+1]],1,n,y+1,-1); swap(a[y],a[y+1]);} 
	}
}
#include<bits/stdc++.h>
#define REP(i,a,b) for(int i(a);i<=(b);++i)
#define mid ((l+r)>>1)
using namespace std;
struct node{int rc,lc,num;}tree[14000010];
int cnt,n,m,a[1000010],rt[1000010];
void read(int &x){
   x=0; char c=getchar(); int f=1; 
   for(;!isdigit(c);c=getchar()) if (c=='-') f=-f;
   for(;isdigit(c);c=getchar()) x=x*10+c-'0';x*=f;}
void update(int &now,int root,int l,int r,int pla){
   now=++cnt;tree[now].num=tree[root].num+1; if (l==r) return; tree[now].lc=tree[root].lc; tree[now].rc=tree[root].rc;
   if (pla<=mid) update(tree[now].lc,tree[root].lc,l,mid,pla); else update(tree[now].rc,tree[now].rc,mid+1,r,pla);}
int search(int now,int root,int l,int r,int pla){
   if(l==r) return l;
   if (tree[tree[now].lc].num-tree[tree[root].lc].num>pla) return search(tree[now].lc,tree[root].lc,l,mid,pla);
   else if (tree[tree[now].rc].num-tree[tree[root].rc].num>pla) return search(tree[now].rc,tree[root].rc,mid+1,r,pla);
   return 0;}
int main(){
   read(n);read(m);
   REP(i,1,n) read(a[i]),update(rt[i],rt[i-1],1,n,a[i]); 
   REP(i,1,m){
   	int x,y; read(x),read(y);
   	printf("%d\n",search(rt[y],rt[x-1],1,n,y-x+1>>1));
   }
}

最後還可以做一下那個經典的K-th number

																				THE   END