整体二分

整体二分

区间第 \(k\) 小问题 一组询问

给定长度为 \(n\) \((n \leq 2 \times 10 ^ 5)\) 的数组 \(a_i\) 。有一组询问,求 \([l,r]\) 区间内第 \(k\) 小的数。

这个题有非常多种做法:

  1. 直接对这个区间排序然后取出第 \(k\) 个数即可,复杂度 \(O(n \log n)\)

  2. 二分答案,设当前二分到的区间是 \([L,R]\) , 设 \(mid = \frac{L+R}{2}\) ,计算 \([l,r]\) 中有一个 \(\leq mid\) 的数,时间复杂度 \(O(n \log V)\)

区间第 \(k\) 小问题 多组询问

问题

给定长度为 \(n\) \((n \leq 2 \times 10 ^ 5)\) 的数组 \(a_i\) 。有 \(q\) \((q \leq 2 \times 10 ^5)\) 组询问,求 \([l,r]\) 区间内第 \(k\) 小的数。

解法

这个题可以用主席树,但是空间复杂度较高,这里介绍整体二分做法。

我们考虑上一个题的第二个做法,对每一组询问二分,但这样时间复杂度是 \(O(nq \log n)\) 的。

我们考虑优化,发现有很多组询问的 \([L,R]\) 是相同的。

比如第一次二分的区间都是 \([1,V]\) ,第二次就会变成 \([1,\frac{V}{2}]\)\([\frac{V}{2} +1,V]\)

我们可以把二分的区间相同的询问一起处理,用一个数据结构维护。

这样就会有一个类似树状的结构。

每一次我们把小于 \(mid\) 的数丢到左侧,大于 \(mid\) 的数丢到右侧。

这样复杂度就会将至 \(O((n+q)\log ^2 n)\)

代码

#include<bits/stdc++.h>
using namespace std;
const int N=2e5+100;
int a[N],mp[N],n,ans[N],t[N],m;
struct Num{int x,p;}num[N];
struct ask{int l,r,k,id;}q[N];
int lowbit(int x){return x&(-x);}
void update(int x,int k){for(int i=x;i<=n;i+=lowbit(i))t[i]+=k;}
int query(int x){int ans=0;for(int i=x;i;i-=lowbit(i))ans+=t[i];return ans;}
void dfs(int l,int r,int numl,int numr,int askl,int askr){
	if(l>r)return;
	if(l==r){
		for(int i=askl;i<=askr;i++)ans[q[i].id]=l;
		return;
	}
	int mid=(l+r)/2,nnuml=numl-1,naskl=askl-1;
	for(int i=numl;i<=numr;i++){
		if(num[i].x<=mid){
			update(num[i].p,1);
			swap(num[++nnuml],num[i]);//把小于mid的数丢到左侧,大于mid的就在右侧了
		}
	}
	for(int i=askl;i<=askr;i++){
		int p=query(q[i].r)-query(q[i].l-1);
		if(p>=q[i].k){
			swap(q[++naskl],q[i]);//把小于mid的询问丢到左侧,大于mid的询问就在右侧
		}else q[i].k-=p;
	}
	for(int i=numl;i<=nnuml;i++)update(num[i].p,-1);
	dfs(l,mid,numl,nnuml,askl,naskl);
	dfs(mid+1,r,nnuml+1,numr,naskl+1,askr);
}
int main(){
	scanf("%d %d",&n,&m);
	for(int i=1;i<=n;i++)scanf("%d",&a[i]);
	for(int i=1;i<=n;i++)mp[i]=a[i];
	sort(mp+1,mp+n+1);
	int nn=unique(mp+1,mp+n+1)-mp-1;
	for(int i=1;i<=n;i++)a[i]=lower_bound(mp+1,mp+nn+1,a[i])-mp;
	for(int i=1;i<=n;i++)num[i]={a[i],i};
	for(int i=1,l,r,k;i<=m;i++){
		scanf("%d %d %d",&l,&r,&k);
		q[i]={l,r,k,i};
	}
	dfs(1,nn,1,n,1,m);
	for(int i=1;i<=m;i++)printf("%d\n",mp[ans[i]]);
}

整体二分的一般思路

整体二分需要结合分治思想。

  1. 计算分类到 \([l,r]\) 的修改对询问的影响。

  2. 不带影响递归 \([l,mid]\)

  3. 带影响递归 \([mid+1,r]\)

例题

P3527 [POI 2011] MET-Meteors

题意

有一个长度为 \(m\) 的环形数列,第 \(i\) 个位置属于第 \(o_i\) 个国家。

\(k\) 次事件,每次会将一个区间内的数加上一个正整数。

每一个国家有一个期望值 \(p_i\) ,求每一个在数列上的位置的数的和在第几次事件后不小于 \(p_i\)

其中,\(1 \leq n,m,k \leq 3 \times 10 ^ 5\)

解法

二分时间即可,和区间第 \(k\) 小几乎一样,复杂度 \(O(n \log ^ 2 n)\)

代码

#include<bits/stdc++.h>
typedef unsigned long long ll;
using namespace std;
const int N=3e5+100,mod=1e9+7;
struct ask{
	vector<int>pos;
	int id;ll p;
}q[N];
struct change{
	int l,r,t;ll a;
}ch[N*2];
ll t[N];
int n,m,k,ans[N],cnt;
int lowbit(int x){return x&(-x);}
void update(int x,ll k){if(x==0)return;for(int i=x;i<=m;i+=lowbit(i))t[i]+=k;}
ll query(int x){ll ans=0;for(int i=x;i;i-=lowbit(i))ans+=t[i];return ans;}
void dfs(int l,int r,int L,int R,int cl,int cr){
	if(l==r){
		for(int i=L;i<=R;i++)ans[q[i].id]=l;
		return;
	}
	int nL=L-1,ncl=cl-1,mid=(l+r)/2;
	for(int i=cl;i<=cr;i++){
		if(ch[i].t<=mid){
			update(ch[i].l,ch[i].a);
			update(ch[i].r+1,-ch[i].a);
			swap(ch[++ncl],ch[i]);
		}
	}
	for(int i=L;i<=R;i++){
		ll p=0;
		for(int j:q[i].pos)p+=query(j);
		if(p>=q[i].p){
			swap(q[++nL],q[i]);
		}else q[i].p-=p;
	}
	for(int i=cl;i<=ncl;i++){
		update(ch[i].l,-ch[i].a);
		update(ch[i].r+1,ch[i].a);
	}
	sort(ch+ncl+1,ch+cr+1,[](change a,change b){return a.t<b.t;});
	dfs(l,mid,L,nL,cl,ncl);dfs(mid+1,r,nL+1,R,ncl+1,cr);
}
int main(){
	scanf("%d %d",&n,&m);
	for(int i=1,x;i<=m;i++){
		scanf("%d",&x);
		q[x].pos.push_back(i);
	}
	for(int i=1,x;i<=n;i++){
		scanf("%d",&x);
		q[i].p=x;
		q[i].id=i;
	}
	scanf("%d",&k);
	for(int i=1,l,r,a;i<=k;i++){
		scanf("%d %d %d",&l,&r,&a);
		if(l<=r)ch[++cnt]={l,r,i,a};
		else{
			ch[++cnt]={1,r,i,a};
			ch[++cnt]={l,m,i,a};
		}
	}
	dfs(1,k+1,1,n,1,cnt);
	for(int i=1;i<=n;i++){
		if(ans[i]==k+1){
			printf("NIE\n");
		}else printf("%d\n",ans[i]);
	}
}

P1527 [国家集训队] 矩阵乘法

题意

有一个 \(n \times n\) 的矩阵,有 \(q\) 次询问,求一个子矩阵中第 \(k\) 小的数。

其中, \(1 \leq n \leq 500\)\(1 \leq q \leq 6 \times 10 ^ 4\)

解法

没什么好说的,二分答案,用二维树状数组维护即可,复杂度 \(O((n ^ 2 + q)\log ^3 n)\)

代码

#include<bits/stdc++.h>
typedef long long ll;
using namespace std;
const int N=510,M=6e4+100;
int n,m,t[N][N],ans[M],cnt,tmp[N*N];
struct Num{
	int x,y,a;
}a[N*N];
struct Ask{
	int x1,y1,x2,y2,id,k;
}q[M];
inline int lowbit(int x){return x&(-x);}
void update(int x,int y,int k){
	for(int i=x;i<=n;i+=lowbit(i)){
		for(int j=y;j<=n;j+=lowbit(j)){
			t[i][j]+=k;
		}
	}
}
int query(int x,int y){
	int res=0;
	for(int i=x;i;i-=lowbit(i)){
		for(int j=y;j;j-=lowbit(j)){
			res+=t[i][j];
		}
	}
	return res;
}
void dfs(int l,int r,int L,int R,int nl,int nr){
	if(l==r){
		for(int i=L;i<=R;i++)ans[q[i].id]=l;
		return;
	}
	int mid=(l+r)/2,nL=L-1,nnl=nl-1;
	for(int i=nl;i<=nr;i++){
		if(a[i].a<=mid){
			update(a[i].x,a[i].y,1);
			swap(a[++nnl],a[i]);
		}
	}
	for(int i=L;i<=R;i++){
		int p=query(q[i].x2,q[i].y2)-query(q[i].x2,q[i].y1-1)-query(q[i].x1-1,q[i].y2)+query(q[i].x1-1,q[i].y1-1);
		if(p>=q[i].k){
			swap(q[++nL],q[i]);
		}else q[i].k-=p;
	}
	for(int i=nl;i<=nnl;i++){
		if(a[i].a<=mid)update(a[i].x,a[i].y,-1);
	}
	dfs(l,mid,L,nL,nl,nnl);dfs(mid+1,r,nL+1,R,nnl+1,nr);
}
int main(){
	scanf("%d %d",&n,&m);
	for(int i=1,x;i<=n;i++){
		for(int j=1;j<=n;j++){
			scanf("%d",&x);
			a[++cnt]={i,j,x};
			tmp[cnt]=x;
		}
	}
	sort(tmp+1,tmp+cnt+1);
	int nn=unique(tmp+1,tmp+cnt+1)-tmp-1;
	for(int i=1;i<=n*n;i++)a[i].a=lower_bound(tmp+1,tmp+nn+1,a[i].a)-tmp;
	for(int i=1;i<=m;i++){
		scanf("%d %d %d %d %d",&q[i].x1,&q[i].y1,&q[i].x2,&q[i].y2,&q[i].k);
		q[i].id=i;
	}
	dfs(1,n*n,1,m,1,n*n);
	for(int i=1;i<=m;i++)printf("%d\n",tmp[ans[i]]);
}

P2617 Dynamic Rankings

题意

给定一个含有 \(n\) 个数的序列 \(a_1,a_2 \dots a_n\),需要支持两种操作共 \(m\) 次:

  • Q l r k 表示查询下标在区间 \([l,r]\) 中的第 \(k\) 小的数;
  • C x y 表示将 \(a_x\) 改为 \(y\)

其中, \(1 \leq n,m \leq 1 \times 10 ^ 5\)

解法

依旧二分答案,把修改和查询一起二分,注意按时间排序,复杂度 \(O((n + m) \log ^ 2 (n + m))\)

代码

#include<bits/stdc++.h>
typedef long long ll;
using namespace std;
const int N=1e5+100;
struct ask{
	int op,x,y,id,k,t;
}q[N*3];
int n,m,Q,mp[N*2],cnt,nn,ans[N],t[N],a[N];
int lowbit(int x){return x&(-x);}
void update(int x,int k){for(int i=x;i<=n;i+=lowbit(i))t[i]+=k;}
int query(int x){int ans=0;for(int i=x;i;i-=lowbit(i))ans+=t[i];return ans;}
void dfs(int l,int r,int L,int R){
	if(l==r){
		for(int i=L;i<=R;i++)ans[q[i].id]=l;
		return;
	}
	int mid=(l+r)/2,nL=L-1,p;
	for(int i=L;i<=R;i++){
		if(q[i].op==0){
			p=query(q[i].y)-query(q[i].x-1);
			if(p>=q[i].k)swap(q[++nL],q[i]);
			else q[i].k-=p;
		}else if(q[i].op==1){
			if(q[i].y<=mid){
				update(q[i].x,-1);
				swap(q[++nL],q[i]);
			}
		}else{
			if(q[i].y<=mid){
				update(q[i].x,1);
				swap(q[++nL],q[i]);
			}
		}
	}
	for(int i=L;i<=nL;i++){
		if(q[i].op==2&&q[i].y<=mid)update(q[i].x,-1);
		if(q[i].op==1&&q[i].y<=mid)update(q[i].x,1);
	}
	sort(q+nL+1,q+R+1,[](ask a,ask b){return a.t<b.t;});
	dfs(l,mid,L,nL);dfs(mid+1,r,nL+1,R);
}
int main(){
	scanf("%d %d",&n,&Q);
	for(int i=1;i<=n;i++){
		scanf("%d",&a[i]);
		q[++m]={2,i,a[i],0,0,m};
		mp[++nn]=a[i];
	}
	char op;
	for(int i=1,l,r,c;i<=Q;i++){
		scanf(" %c %d %d",&op,&l,&r);
		if(op=='Q'){
			scanf("%d",&c);
			q[++m]={0,l,r,++cnt,c,m};
		}else{
			mp[++nn]=r;
			q[++m]={1,l,a[l],0,0,m};
			q[++m]={2,l,r,0,0,m};
			a[l]=r;
		}
	}
	sort(mp+1,mp+nn+1);
	nn=unique(mp+1,mp+nn+1)-mp-1;
	for(int i=1;i<=m;i++){
		if(q[i].op==1||q[i].op==2){
			q[i].y=lower_bound(mp+1,mp+nn+1,q[i].y)-mp;
		}
	}
	dfs(1,nn,1,m);
	for(int i=1;i<=cnt;i++)printf("%d\n",mp[ans[i]]);
}

P3332 [ZJOI2013] K 大数查询

题意

你需要维护 \(n\) 个可重整数集,集合的编号从 \(1\)\(n\)
这些集合初始都是空集,有 \(m\) 个操作:

  • 1 l r c:表示将 \(c\) 加入到编号在 \([l,r]\) 内的集合中
  • 2 l r c:表示查询编号在 \([l,r]\) 内的集合的并集中,第 \(c\) 大的数是多少。

注意可重集的并是不去除重复元素的,如 \(\{1,1,4\}\cup\{5,1,4\}=\{1,1,4,5,1,4\}\)

其中, \(1 \leq n,m \leq 5 \times 10 ^ 4\)

解法

先离线,二分第 \(c\) 大的数,用线段树区间加和区间查询维护即可,复杂度 \(O(n \log ^ 2 n)\)

代码

#include<bits/stdc++.h>
#define mid ((l+r)>>1)
#define ls (rt<<1)
#define rs (rt<<1|1)
#define lson ls,l,mid
#define rson rs,mid+1,r
typedef long long ll;
using namespace std;
const int N=5e4+100;
struct tree{
	ll cnt,tag;
}t[N*8];
struct oper{
	int op,l,r,id,t;ll c;
}q[N];
ll ans[N];
int n,m,mx,cnt;
void pushtag(int rt,int l,int r,ll k){
	t[rt].tag+=k;
	t[rt].cnt+=(r-l+1)*k;
}
void pushdown(int rt,int l,int r){
	if(t[rt].tag){
		pushtag(lson,t[rt].tag);
		pushtag(rson,t[rt].tag);
		t[rt].tag=0;
	}
}
void pushup(int rt,int l,int r){
	t[rt].cnt=t[ls].cnt+t[rs].cnt;
}
void update(int rt,int l,int r,int L,int R,ll k){
	if(L<=l&&R>=r){
		pushtag(rt,l,r,k);
		return;
	}
	pushdown(rt,l,r);
	if(L<=mid)update(lson,L,R,k);
	if(R>mid)update(rson,L,R,k);
	pushup(rt,l,r);
}
ll query(int rt,int l,int r,int L,int R){
	if(L<=l&&R>=r)return t[rt].cnt;
	pushdown(rt,l,r);
	if(L<=mid&&R>mid)return query(lson,L,R)+query(rson,L,R);
	if(L<=mid)return query(lson,L,R);
	return query(rson,L,R);
}
void dfs(int l,int r,int L,int R){
	if(l==r){
		for(int i=L;i<=R;i++)if(q[i].op==2)ans[q[i].id]=l;
		return;
	}
	int nL=L-1;
	for(int i=L;i<=R;i++){
		if(q[i].op==1){
			if(q[i].c>mid){
				update(1,1,n,q[i].l,q[i].r,1);
				swap(q[++nL],q[i]);
			}
		}else{
			ll p=query(1,1,n,q[i].l,q[i].r);
			if(p>=q[i].c){
				swap(q[++nL],q[i]);
			}else q[i].c-=p;
		}
	}
	for(int i=L;i<=nL;i++){
		if(q[i].op==1){
			update(1,1,n,q[i].l,q[i].r,-1);
		}
	}
	sort(q+nL+1,q+R+1,[](oper a,oper b){return a.t<b.t;});
	dfs(mid+1,r,L,nL);dfs(l,mid,nL+1,R);
}
int main(){
	scanf("%d %d",&n,&m);
	for(int i=1,op,l,r;i<=m;i++){
		ll c;
		scanf("%d %d %d %lld",&op,&l,&r,&c);
		if(op==2)cnt++;
		q[i]={op,l,r,cnt,i,c};
	}
	dfs(-n,n,1,m);
	for(int i=1;i<=cnt;i++)printf("%lld\n",ans[i]);
}
posted @ 2026-09-13 21:44  _lipeiran  阅读(10)  评论(0)    收藏  举报