整体二分

整体二分


当对于一个多询问且可以通过二分答案解决的问题,可以通过整体二分来优化复杂度。

例1 区间第K值

Description

多询问,每次询问区间的第K大值

Solution

  • 二分权值V,把权值\(\le\)V的点加入BIT中,然后询问第K大值\(\le\)V的放左边,>V的放右边
  • 右边的K要减去区间中权值\(\le\)V的点的个数
  • 然后分治下去即可
  • 复杂度\(O(qlog^2n)\)
#include<cstdio>
#include<algorithm>
using namespace std;
#define FOR(i,x,y) for(int i=(x),i##_END=(y);i<=i##_END;++i)
#define M 30005
int A[M],B[M],Id[M],Ans[M];
struct GG{
	int x,y,k,id;
}Q[M],Q1[M],Q2[M];
bool cmp(int x,int y){
	return A[x]<A[y];
}

struct BIT{
	int Sum[M];
	void add(int x,int a){while(x<M){Sum[x]+=a;x+=x&-x;}}
	int query(int x){int res=0;while(x){res+=Sum[x];x-=x&-x;}return res;}
}BIT;

void Solve(int l,int r,int Ql,int Qr,int L,int R){
	if(Ql>Qr||l>r)return;
	if(L==R){
		FOR(i,Ql,Qr)Ans[Q[i].id]=L;
		return;
	}
	int mid=(L+R)>>1,las=r;
	FOR(i,l,r){
		if(A[Id[i]]<=mid){
			BIT.add(Id[i],1);
		}else {las=i-1;break;}
	}
	int c1=Ql-1,c2=Qr;
	FOR(i,Ql,Qr){
		int s=BIT.query(Q[i].y)-BIT.query(Q[i].x-1);
		if(s>=Q[i].k)Q1[++c1]=Q[i];
		else Q[i].k-=s,Q2[c2--]=Q[i];
	}
	FOR(i,Ql,c1)Q[i]=Q1[i];
	FOR(i,c2+1,Qr)Q[i]=Q2[i];
	FOR(i,l,las)BIT.add(Id[i],-1);
	Solve(l,las,Ql,c1,L,mid);
	Solve(las+1,r,c2+1,Qr,mid+1,R);
}

int main(){
	int n,m;
	scanf("%d%d",&n,&m);
	FOR(i,1,n)scanf("%d",&A[i]),B[i]=A[i],Id[i]=i;
	FOR(i,1,m){
      scanf("%d%d%d",&Q[i].x,&Q[i].y,&Q[i].k);
   	Q[i].k=Q[i].y-Q[i].x+2-Q[i].k,Q[i].id=i;
   }
	sort(B+1,B+n+1);
	int c=unique(B+1,B+n+1)-B-1;
	FOR(i,1,n)A[i]=lower_bound(B+1,B+c+1,A[i])-B;
	sort(Id+1,Id+n+1,cmp);
	Solve(1,n,1,m,1,c);
	FOR(i,1,m)printf("%d\n",B[Ans[i]]);
	return 0;
}

例2 K大数查询

Description

\(n\)个位置和\(m\)个操作。操作有两种:

如果操作形如1 a b c,表示往第\(a\)个位置到第\(b\)个位置每个位置加入一个数\(c\)

如果操作形如 2 a b c,表示询问从第\(a\)个位置到第\(b\)个位置,第\(c\)大的数是多少?

Solution

  • 二分权值V,根据时间排序(顺序)
  • 然后对于操作1,\(c\le v\)的放左边,其余的放右边。
  • 对于操作2,每次区间查询 \([l,r]\) 中满足条件的点的个数S。
  • \(K\le S\)的询问放左边,其余的放右边,同样右边的询问要减去当前的贡献。

例3 HDU5412 CRB and Queries

Description

\(n\)个位置和\(m\)个操作。操作有两种:

如果操作形如1 a b ,表示把第\(a\)个的数变成\(b\)

如果操作形如 2 a b c,表示询问从第\(a\)个位置到第\(b\)个位置,第\(c\)小的数是多少?

  • 二分权值,根据时间顺序排序
  • 通过拆点把操作转化成在某个位置(x)加入/删除一个权值为v的点
  • 然后和例2的操作相同,利用BIT单点更新区间查询
#include<cstdio>
#include<algorithm>
using namespace std;
#define FOR(i,x,y) for(int i=(x),i##_END=(y);i<=i##_END;++i)
#define DOR(i,x,y) for(int i=(x),i##_END=(y);i>=i##_END;--i)

const int N=100005;

struct node{
	int op,l,r,v;
}Q[N*3],Q1[N*3];
int A[N],B[N<<1],Ans[N];

int Sum[N];
void update(int x,int v){
	while(x<N){Sum[x]+=v;x+=x&-x;}
}
int query(int x){
	int res=0;
	while(x){res+=Sum[x];x-=x&-x;}
	return res;
}

void Solve(int l,int r,int L,int R){
	if(l>r)return;
	if(L==R){
		FOR(i,l,r)if(Q[i].op)Ans[Q[i].op]=B[L];
		return;
	}
	int mid=(L+R)>>1;
	int t1=l-1,t2=r;
	FOR(i,l,r){
		if(!Q[i].op){
			if(Q[i].v<=mid){
				update(Q[i].l,Q[i].r);
				Q1[++t1]=Q[i];
			}else Q1[t2--]=Q[i];
		}else{
			int s=query(Q[i].r)-query(Q[i].l-1);
			if(Q[i].v<=s)Q1[++t1]=Q[i];
			else {
				Q[i].v-=s;
				Q1[t2--]=Q[i];
			}
		}
	}
	if(t2<=r)reverse(Q1+t2+1,Q1+r+1);
	FOR(i,l,r)Q[i]=Q1[i];
	FOR(i,l,t1)if(!Q[i].op)update(Q[i].l,-Q[i].r);
	Solve(l,t1,L,mid);
	Solve(t2+1,r,mid+1,R);
}

int main(){
	int n,q;
	while(~scanf("%d",&n)){
		int m=0,c=0;
	
		FOR(i,1,n){
			int x;
			scanf("%d",&x);
			Q[++m]=(node){0,i,1,x};
			B[++c]=x,A[i]=x;
		}
		
		scanf("%d",&q);
		FOR(i,1,q){
			int op,l,r,v;
			scanf("%d",&op);
			if(op==1){
				scanf("%d%d",&l,&v);
				Q[++m]=(node){0,l,-1,A[l]};
				A[l]=v;
				Q[++m]=(node){0,l,1,A[l]};
				B[++c]=v;
			}
			else {
				scanf("%d%d%d",&l,&r,&v);
				Q[++m]=(node){i,l,r,v};
			}
		}
		
		sort(B+1,B+c+1);
		c=unique(B+1,B+c+1)-B-1;
		
		FOR(i,1,m)if(Q[i].op==0){
			Q[i].v=lower_bound(B+1,B+c+1,Q[i].v)-B;
		}
		
		FOR(i,1,q)Ans[i]=-1;
		Solve(1,m,1,c);
		FOR(i,1,q)if(Ans[i]!=-1)printf("%d\n",Ans[i]);
	}
	
	return 0;
}
posted @ 2018-09-27 22:16  Zerokei  阅读(330)  评论(0编辑  收藏  举报