主席树各部分,各个类别讲解
注释: 本篇只对对于主席树有“足够”认识的人浏览,对于主席树的原理,思想,本人因为是复习笔记,一概不谈。
作用类讲解
- 像你谷这道题数颜色所用的就是主席树的动态修改和查询(线段树版的)所以可以logn查询很快。。
- 而像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 输出来吧
下面贴代码
- 洛谷主席树模板绝对是复习第一站的好题;
#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;
}
}
- 数颜色 线段树查询类。。。难度较小
#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]);}
}
}
- P3567 [POI2014]KUR-Couriers和K—th number 思想挺想的
#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