主席树部分小总结

主席树各部分,各个类别讲解

注释: 本篇只对对于主席树有“足够”认识的人浏览,对于主席树的原理,思想,本人因为是复习笔记,一概不谈。

作用类讲解

  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]);} 
	}
}
  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
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值