主席树模板--POJ 2104 第K大 + SPOJ D-query区间次数和

主席树一般是用来记录没个数的出现次数,类似权值线段树,与普通线段树不同的是它每次只用更新一条链

不能再用堆存储,但是可以根据根结点编号定位,建立l,r,rts数组

rts存每个位置的根节点编号,快速定位,l,r数组存储该根节点编号的左右儿子编号

 

add操作写法变化不大,query写法看情况,目前有求第K大与求和两种写法

 

第一题是递归找区间第K大,第二题是求区间出现次数和,第三题前两题综合,第四题和第三题差不多但在线操作,不用离散化

第一题比较特殊,只用找出第K大,因此顺下去找就行了

但有些要求区间数字出现次数和,要么版本相减,要么可以加个pushup操作(回溯)

 

POJ 2104

感谢奇奇的模板

#include<cstdio>
#include<algorithm>
using namespace std;
#define lson l,m,ls[rt]
#define rson m+1,r,rs[rt] 
typedef long long ll;
const int maxn = 1e5 + 5;
int ls[maxn*30],rs[maxn*30],tot,rts[maxn];//tot为总结点数 ,rts为每个位置的线段树的根
int t[maxn*30];//重点!权值线段树,存的是每个数出现次数
int a[maxn],num[maxn];//原数组和离散化的

void build(int l,int r,int &rt)
{
	rt = ++tot;//第几个结点
	t[rt] = 0; 
	if(l==r)		
		return ;
	int m = l+r>>1;
	build(lson); 
	build(rson);
}

void add(int p,int C,int l,int r,int &rt,int lst)//rt是引用调用
{
	rt = ++tot;
	ls[rt] = ls[lst],rs[rt] = rs[lst];
	t[rt] = t[lst] + C;	//上一个版本的结点拷贝信息,一般C=1,表示出现次数+1
	if(l==r)						
		return ;
	int m = l+r>>1;
	if(p<=m)
		add(p,C,lson,ls[rt]);
	else
		add(p,C,rson,rs[rt]);
}

int query(int x,int y,int l,int r,int k)
{
	if(l==r)
		return l;
	int c = t[ls[y]] - t[ls[x]];//c表示左子树中数字的数量 
	int m = l+r>>1;
	if(c>=k)
		return query(ls[x],ls[y],l,m,k);
	else  //左子树不够k,从右子树找
		return query(rs[x],rs[y],m+1,r,k-c);	
}

int N,M,c,x,y,k;
int main()
{
	while(scanf("%d%d",&N,&M)!=EOF)
	{
		tot = 0;	
		for(int i=1;i<=N;++i)
		{
			scanf("%d",&a[i]);
			num[i] = a[i];
		}
		
		sort(num+1,num+N+1);
		int cnt = unique(num+1,num+N+1)-(num+1);
		build(1,cnt,rts[0]);
		for(int i=1;i<=N;++i)
			a[i] = lower_bound(num+1,num+cnt+1,a[i])-num;//离散化
		
		for(int i=1;i<=N;++i)
			add(a[i],1,1,cnt,rts[i],rts[i-1]);
					
		while(M--)
		{
			scanf("%d%d%d",&x,&y,&k);
			printf("%d\n",num[query(rts[x-1],rts[y],1,cnt,k)]);
		}
	}	
	return 0;
}

 

SPOJ D-query

不同种类和,同种只能算一次

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define lson l,mid,ls[rt]
#define rson mid+1,r,rs[rt] 
const int maxn=1e5+10;
int a[300005];
int num[300005];
int ls[maxn*30],rs[maxn*30],rts[maxn];
int t[maxn*30];
int vis[maxn];//额外记录上一个重复元素位置 
int tot,ans;

void build(int l,int r,int &rt)
{
	rt=++tot;
	t[rt]=0;
	if(l==r)return;
	int mid=l+r>>1;
	build(lson);
	build(rson);
}

void add(int p,int C,int l,int r,int &rt,int lst) 
{
	rt=++tot;
	ls[rt]=ls[lst],rs[rt]=rs[lst];
	t[rt]=t[lst]+C;
	if(l==r)return;
	
	int mid=l+r>>1;
	if(p<=mid)
	   add(p,C,lson,ls[rt]);
	else
	   add(p,C,rson,rs[rt]);
	
}

void query(int x,int y,int l,int r,int rt)//注意这里写法 
{
	if(l==x)
	{
	//	return t[rt];
	ans+=t[rt];
	return ;
	}
	//int c=t[ls[y]]-t[ls[x]];
	
	int mid=l+r>>1;
	if(x<=mid) 
	   ans+=t[rs[rt]],query(x,y,lson);
//	if(y>mid)
    else
	   query(x,y,rson);

}

int main()
{
  int n;
  cin>>n;
  for(int i=1;i<=n;i++){
  	scanf("%d",&a[i]); 
  	num[i]=a[i];
  } 
  
  	sort(num+1,num+n+1);
		int cnt = unique(num+1,num+n+1)-(num+1);
	//	build(1,cnt,rts[0]);
		for(int i=1;i<=n;++i)
			a[i] = lower_bound(num+1,num+cnt+1,a[i])-num;
	int tmp;
	for(int i=1;i<=n;i++){      //这里要考虑重复元素 ,重复的话只保留最后一个
	if(!vis[a[i]])
		add(i,1,1,n,rts[i],rts[i-1]);
	else{
		add(vis[a[i]],-1,1,n,tmp,rts[i-1]);//先删除再添加 
		add(i,1,1,n,rts[i],tmp);
	}
		vis[a[i]]=i;
	}		
	
	int q,x,y;
	cin>>q;
	while(q--)
	{
		ans=0;
		scanf("%d%d",&x,&y);
		query(x,y,1,n,rts[y]);
		printf("%d\n",ans);
	}
	
  
  
}

query写法参考了一下

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值