POJ2104 HDU2665 K-th Number【主席树】

K − t h   N u m b e r K-th\ Number Kth Number

之前在《挑战》上看到求区间第K大的办法是用分块做的,但是一直TLE过不了,看讨论里都是主席树,然后觉得很难就一直没学,今天终于花了一晚上搞懂了静态主席树是什么了,其实也不是特别难懂。
权值线段树和普通线段树差不多,权值线段树每个节点表示的是[L,R]之间各个数出现的次数。
主席树其实就是建立在权值线段树结构上的一种可持久化的数据结构,用于解决区间第K大问题,可持久化数据结构的意思就是这个数据结构保存了更新以前的各种版本,而对于区间第K大的问题,每个版本表示得失区间[1,i]的一棵权值线段树,这样如果要求区间[L,R]第K大的问题,就像计算前缀和一样,把第R棵权值线段树和第L-1棵相减,就得到了区间[L,R]的权值线段树,在这棵线段树上求解区间第K大问题就好了,说了这么多,但是怎么保存之前的版本呢,最粗暴的办法就是每次更新就新建一棵线段树,但是这样的话,肯定会双LE,所以要找其他的方法。
仔细分析一下不难发现线段树每次更新的节点都是在一条链上的,所以其实没有必要每次更新都重建一棵线段树,只要重建被更改的那条链就好了。
由于出现的数范围可能比较大,一般需要先进行离散化,所以这是一种离线数据结构。
具体看一下代码,比较清楚点:

PKU2104 K-th Number

http://poj.org/problem?id=2104

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
using namespace std;
const int maxn = 1e5+7;
vector<int> vec;			//用于离散化
int n,m,root[maxn],seq[maxn];
int getid(int x){return lower_bound(vec.begin(),vec.end(),x)-vec.begin()+1;} //离散化之后的编号1~maxid
struct hjt_Tree{		//把主席树封装成结构体
	struct Node{		//节点结构体
		int l,r,sum;
	}node[maxn*30];
	int cnt;			//节点数量
	hjt_Tree(){cnt = 0;}
	void update(int l,int r,int &now,int pre,int pos){//更新(添加数)操作
		node[++cnt]=node[pre];
		node[now=cnt].sum++;
		if(l+1==r) return;
		int mid = (l+r)>>1;
		if(pos<mid) update(l,mid,node[now].l,node[pre].l,pos);
		else update(mid,r,node[now].r,node[pre].r,pos);
	}
	int query(int l,int r,int x,int y,int k){			//求区间[l,r]第K大
		if(l+1==r) return l;
		int mid = (l+r)>>1;
		int sum = node[node[y].l].sum-node[node[x].l].sum;	//左子树上权值和
		if(sum>=k) return query(l,mid,node[x].l,node[y].l,k);
		else return query(mid,r,node[x].r,node[y].r,k-sum);
	}
}Seg_Tree;
int main(){
	scanf("%d %d",&n,&m);
	for(int i=1;i<=n;i++) scanf("%d",&seq[i]),vec.push_back(seq[i]);
	sort(vec.begin(),vec.end());
	vec.erase(unique(vec.begin(),vec.end()),vec.end());//去重,构成离散化后的查询表
	for(int i=1;i<=n;i++) Seg_Tree.update(1,n+1,root[i],root[i-1],getid(seq[i]));
	for(int i=1;i<=m;i++){
		int l,r,k;
		scanf("%d %d %d",&l,&r,&k);
		printf("%d\n",vec[Seg_Tree.query(1,n+1,root[l-1],root[r],k)-1]);
	}
	return 0;
}
HDU2665 Kth number

http://acm.hdu.edu.cn/showproblem.php?pid=2665

#include<cstdio>
#include<cstring>
#include<vector>
#include<algorithm>
using namespace std;
const int maxn = 1e5+7;
int T,n,m,seq[maxn],root[maxn];
vector<int> vec;					//用于离散化
int getid(int x){return lower_bound(vec.begin(),vec.end(),x)-vec.begin()+1;}//查询离散化后的ID
struct ljt_Tree{												//封装主席树结构体
	struct Node{
		int l,r,sum;
	}node[maxn<<5];
	int cnt;
	void init(){cnt=0;}
	void update(int l,int r,int &now,int pre,int pos){			//更新操作
		node[++cnt]=node[pre];									//复制一份前驱节点-->变成新节点
		node[now=cnt].sum++;
		if(l+1==r) return;
		int mid = (l+r)>>1;
		if(pos<mid) update(l,mid,node[now].l,node[pre].l,pos);
		else update(mid,r,node[now].r,node[pre].r,pos);
	}
	int query(int l,int r,int x,int y,int k){					//查找区间[l,r]第k大
		if(l+1==r) return l;
		int mid = (l+r)>>1;
		int sum = node[node[y].l].sum-node[node[x].l].sum;		//左子树权值和
		if(sum>=k) return query(l,mid,node[x].l,node[y].l,k);
		else return query(mid,r,node[x].r,node[y].r,k-sum);
	}
}Seg_Tree;
int main(){
	scanf("%d",&T);
	while(T--){
		vec.clear();
		Seg_Tree.init();
		scanf("%d %d",&n,&m);
		for(int i=1;i<=n;i++) scanf("%d",&seq[i]),vec.push_back(seq[i]);
		sort(vec.begin(),vec.end());
		vec.erase(unique(vec.begin(),vec.end()),vec.end());					//去重
		for(int i=1;i<=n;i++) Seg_Tree.update(1,n+1,root[i],root[i-1],getid(seq[i]));	//建树
		for(int i=1;i<=m;i++){													//查询操作
			int l,r,k;
			scanf("%d %d %d",&l,&r,&k);
			printf("%d\n",vec[Seg_Tree.query(1,n+1,root[l-1],root[r],k)-1]);
		}
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值