整体二分--poj2104静态区间第k小

传送门

 昨天跟yousiki学到了整体二分!quq

最经典的就是静态区间第k小(大)

听说2013许昊然的论文-《浅谈数据结构题的几个非经典解法》 讲的特别好,从网上找了个图


复杂度很优秀的说!

具体做法一般是:

1、找到询问答案的上限和下限limit,开始二分

2、每次二分出一个mid,判断答案在l~mid之间的询问,如果有修改就判断修改的影响是否在l~mid之间,然后将l到r内的操作分成两部分,左边是l~mid之间的,右边是mid+1~r之间的

3、分别递归求解两部分的答案

4、如果二分的val值vl==vr,l~r内询问的答案已经确定,为vl,return

5、判断部分一般需要数据结构支持

 

这道题的做法也差不多啦,数据结构支持单点加和区间求和,用树状数组就可以

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#define M 50005
#define N 100005
using namespace std;
int n,m,f[N],mx,ans[M];

inline int rd(){
	int x=0,f=1;char c=' ';
	while(c<'0' || c>'9') {if(c=='-')f=-1;c=getchar();}
	while(c<='9' && c>='0') x=x*10+c-'0',c=getchar();
	return x*f;
}

struct qwq{
	int val,num;
}a[N];
inline bool cmp(qwq x,qwq y){return x.val<y.val;}
struct Query{
	int cnt,k,num,l,r;
}q[M],b[M];

inline void add(int x,int y){
	for(;x<=n;x+=x&-x)
		f[x]+=y;
}

inline int query(int x){
	int sum=0;
	for(;x;x-=x&-x)
		sum+=f[x];
	return sum;
}

inline void calc(int vl,int vr,int l,int r){
	int ll=1,rr=n+1;
	while(ll<rr){//找到a数组中大于等于vl的最后一个 
		int mid=(ll+rr)>>1;
		if(a[mid].val>=vl) rr=mid;
		else ll=mid+1;
	}
	for(int i=rr;i<=n && a[i].val<=vr;i++)
		add(a[i].num,1);//将小于等于mid的加入 
	for(int i=l;i<=r;i++)
		q[i].cnt=query(q[i].r)-query(q[i].l-1);//计算询问在l~mid的答案 
	for(int i=rr;i<=n && a[i].val<=vr;i++)//记得还原 
		add(a[i].num,-1);
}

inline void solve(int l,int r,int vl,int vr){
	if(vl==vr){
		for(int i=l;i<=r;i++)
			ans[q[i].num]=vl;
		return;
	}
	int mid=(vl+vr)>>1;
	calc(vl,mid,l,r);//计算询问中 
	int now1=l,now2=r;
	for(int i=l;i<=r;i++)//将询问分成两部分 
		if(q[i].cnt>=q[i].k) b[now1++]=q[i];
		else q[i].k-=q[i].cnt,b[now2--]=q[i];
	for(int i=l;i<=r;i++) q[i]=b[i];//合并两部分 
	if(now1!=l) solve(l,now1-1,vl,mid);//递归求解 
	if(now2!=r) solve(now2+1,r,mid+1,vr);
	return;
}

int main(){
	n=rd(); m=rd();
	for(int i=1;i<=n;i++) a[i].val=rd(),a[i].num=i;
	a[n+1].val=1e9+5;
	sort(a+1,a+n+1,cmp);
	for(int i=1;i<=m;i++){
		q[i].num=i; q[i].l=rd(); q[i].r=rd(); q[i].k=rd();
	}
	solve(1,m,-1e9,1e9);
	for(int i=1;i<=m;i++) printf("%d\n",ans[i]);
	return 0;
}

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值