整体二分

整体二分

思想概述

整体二分算法,全称为基于值域的分治算法,它的思想是这样的:
在某些题目中,我们需要处理很多个询问,而每一个询问都需要二分答案,且\(check\)函数类似,在我们进行每一次二分答案的时候,\(check\)函数返回的信息很有可能会被浪费掉,而整体二分就是来解决这个问题的

详细的说,整体二分算法便是离线对所有询问一起同时进行二分答案,然后将符合的与不符合的分为两类,分别递归继续二分答案

一般来讲,整体二分算法的模板伪代码长这样:

solve(int l,int r,int s,int t){  //解决s-t范围内询问的答案(为了节省空间,便出此下策),当前二分区间是l,r
    int mid=l+r>>1;
    ……(使用类似于check的东西统计Mid能带来的信息)
    for(int i=s;i<=t;i++)……(对ask[s-t]和上面得到的信息进行判断是否可行,并分别加入ql,qr两个数组)
    ……(将ql,qr两者合并到原ask[s-t]位置,ql放前面,qr放后面)
    solve(l,mid,s,s+len_ql-1);solve(mid+1,r,len_ql+s,t);
}

我们通过几道题来感受一下这个算法

例题应用

  1. 流星

题目描述

Byteotian Interstellar Union (BIU) has recently discovered a new planet in a nearby galaxy. The planet is unsuitable for colonisation due to strange meteor showers, which on the other hand make it an exceptionally interesting object of study.

The member states of BIU have already placed space stations close to the planet's orbit. The stations' goal is to take samples of the rocks flying by.

The BIU Commission has partitioned the orbit into \(m\) sectors, numbered from \(1\) to \(m\), where the sectors \(1\) and \(m\) are adjacent. In each sector there is a single space station, belonging to one of the \(n\) member states.

Each state has declared a number of meteor samples it intends to gather before the mission ends. Your task is to determine, for each state, when it can stop taking samples, based on the meter shower predictions for the years to come.

输入格式

The first line of the standard input gives two integers, \(n\) and \(m\) (\(1\le n,m\le 300\ 000\)), separated by a single space, that denote,respectively, the number of BIU member states and the number of sectors the orbit has been partitioned into.

In the second line there are \(m\) integers \(o_i\) (\(1\le o_i\le n\)),separated by single spaces, that denote the states owning stations in successive sectors.

In the third line there are \(n\) integers \(p_i\) (\(1\le p_i\le 10^9\)),separated by single spaces, that denote the numbers of meteor samples that the successive states intend to gather.

In the fourth line there is a single integer \(k\) (\(1\le k\le 300\ 000\)) that denotes the number of meteor showers predictions. The following \(k\) lines specify the (predicted) meteor showers chronologically. The \(i\)-th of these lines holds three integers \(l_i,r_i,a_i\) (separated by single spaces), which denote that a meteor shower is expected in sectors \(l_i,l_{i+1},...,r_i\)(if \(l_i\le r_i\)) or sectors \(l_i,l_{i+1},...,m,1,...,r_i\) (if \(l_i>r_i\)) , which should provide each station in those sectors with \(a_i\) meteor samples (\(1\le a_i\le 10^9\)).

输出格式
Your program should print \(n\) lines on the standard output.

The \(i\)-th of them should contain a single integer \(w_i\), denoting the number of shower after which the stations belonging to the \(i\)-th state are expected to gather at least \(p_i\) samples, or the word NIE (Polish for no) if that state is not expected to gather enough samples in the foreseeable future.

题面翻译

Byteotian Interstellar Union

\(n​\) 个成员国。现在它发现了一颗新的星球,这颗星球的轨道被分为 \(m​\) 份(第 \(m​\) 份和第 \(1​\) 份相邻),第 \(i​\) 份上有第 \(a_i​\) 个国家的太空站。

这个星球经常会下陨石雨。BIU 已经预测了接下来 \(k\) 场陨石雨的情况。

BIU 的第 \(i\) 个成员国希望能够收集 \(p_i\) 单位的陨石样本。你的任务是判断对于每个国家,它需要在第几次陨石雨之后,才能收集足够的陨石。

输入格式

第一行是两个数 \(n,m\)

第二行有 \(m\) 个数,第 \(i\) 个数 \(o_i\) 表示第 \(i\) 段轨道上有第 \(o_i\) 个国家的太空站。

第三行有 \(n\) 个数,第 \(i\) 个数 \(p_i\) 表示第 \(i\) 个国家希望收集的陨石数量。

第四行有一个数 \(k\),表示 BIU 预测了接下来的 \(k\) 场陨石雨。 接下来 \(k\) 行,每行有三个数 \(l_i,r_i,a_i\) ,表示第 \(k\) 场陨石雨的发生地点在从 \(l_i\) 顺时针到 \(r_i\) 的区间中(如果 \(l_i \leq r_i\),则是 \(l_i, l_i + 1 \cdots, r_i\),否则就是 \(l_i, l_i + 1, \cdots m - 1, m, 1, 2, \cdots r_i\)),向区间中的每个太空站提供 \(a_i\) 单位的陨石样本。

输出格式

输出 \(n\) 行。第 \(i\) 行的数 \(w_i\) 表示第 \(i\)个国家在第 \(w_i\) 波陨石雨之后能够收集到足够的陨石样本。如果到第 \(k\) 波结束后仍然收集不到,输出NIE

数据范围

\(1\le n,m,k\le 3\cdot10^5\)

\(1\le p_i,a_i\le 10^9\)

思路

先思考,如果仅仅是询问一次该怎么做,很明显可以二分答案,具体步骤是求出在各个时刻共落下了多少陨石雨,稍加判断即可

这个做法也可以稍加更改,变成:对于每一次l=mid之前,让其所需要的总陨石数量减去\(sum[l]\),在每一次判断的时候仅仅判断在\([l,mid]\)区间的时候落下的陨石是否足够即可

对这个做法进行两次扩展,首先是扩展到整体二分算法,我们对于每一个子任务,都进行求前缀和以及判断,但这样做会出现问题,首先是:前缀和如果在外部计算不宜处理,在内计算\([1,mid]\)的前缀和很恶心,所以我们还需要更改,类似于平衡树查排名的操作,将做法变更为上述第二个二分做法,此时,便会涉及到一个区间修改单点查询的操作,使用树状数组维护差分数组解决。综合所述,我们可以设计出如下算法:

  1. 读入值,将每一个国家所有的空间站单独开一个vector储存
  2. 整体二分,进入solve函数
  3. 在solve中有四个参数\((l,r,s,t)\),对于在\([l,mid]\)中的陨石雨所影响的范围在树状数组所维护的差分数组上更改
  4. 对于每一个在\([s,t]\)内的询问,判断是否加上\([l,mid]\)的流星雨后满足要求,满足的加入\(ql\),不满足的让其\(p_i\)减去\([l,mid]\)中所增加的值,加入\(qr\)
  5. 撤销树状数组修改,复制\(ql,qr\)数组,继续分治求解
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<vector>
#define int long long
using namespace std;
vector<int>idx[300005];//第i个国家拥有哪些轨道 
int n,tot,m;
struct node{
	int l,r,pi;
	//当表示轨道时:id属于第id段轨道,第oi个国家,需要pi 
}b[300005];
pair<int,int >a[300005],ql[300005],qr[300004];
int ans[300005],p[300005],k,c[300005],vis[300005];
void add(int x,int y){
	for(;x<=m;x+=x&-x){c[x]+=y;}
}
void add(int l,int r,int d){
	add(l,d);if(r<m)add(r+1,-d);
}
int ask(int k){
	int ans=0;
	for(;k;k-=k&-k)ans+=c[k];
	return ans;
}
void solve(int l,int r,int st,int ed){
	if(st>ed)return ;
	if(l==r){
		for(int i=st;i<=ed;i++){
			ans[a[i].first]=l;
		}
		return;
	}
	int mid=l+r>>1;int lt=0,rt=0;
	for(int i=l;i<=mid;i++){
		if(b[i].l>b[i].r)add(b[i].l,m,b[i].pi),add(1,b[i].r,b[i].pi);
		else add(b[i].l,b[i].r,b[i].pi);
	}
	for(int i=st;i<=ed;i++){
		int len=idx[a[i].first].size(),sum=a[i].second;
		for(int j=0;j<len;j++){
			sum-=ask(idx[a[i].first][j]); 
			if(sum<=0)break;//注意这个细节,不加会出现神币错误
		}
		if(sum<=0)ql[++lt]=a[i];
		else a[i].second=sum,qr[++rt]=a[i];
	}
	for(int i=l;i<=mid;i++){
		if(b[i].l>b[i].r)add(b[i].l,m,-b[i].pi),add(1,b[i].r,-b[i].pi);
		else add(b[i].l,b[i].r,-b[i].pi);
	}
	for(int i=1;i<=lt;i++)a[st+i-1]=ql[i];
	for(int i=1;i<=rt;i++)a[st+lt+i-1]=qr[i];
	solve(l,mid,st,st+lt-1);
	solve(mid+1,r,st+lt,ed);
}
signed main(){
	scanf("%lld%lld",&n,&m);
	for(int i=1;i<=m;i++){
		int o;
		scanf("%lld",&o);
		idx[o].push_back(i);
	}
	for(int i=1;i<=n;i++)scanf("%lld",&a[i].second),a[i].first=i;
	scanf("%d",&k);
	for(int i=1;i<=k;i++)scanf("%lld%lld%lld",&b[i].l,&b[i].r,&b[i].pi);
	b[++k]={1,m,0x3f3f3f3f};
	solve(1,k,1,n);
	for(int i=1;i<=n;i++){
		if(ans[i]!=k)printf("%lld\n",ans[i]);
		else puts("NIE");
	}
}
  1. 动态区间第K大问题
    两个操作,一个是询问给定区间的第K大,一个是单点修改
    首先考虑不带修的问题,可以考虑转化为全局第\(K\)大,然后二分值域,类似于在这颗递归树(也类似于平衡树)上做类似于平衡树的查找,至于单点修改,请读者自己思考
#include<algorithm>
#include<cstring>
#include<cstdio>
#define Re register int
#define LL long long
using namespace std;
const int N=1e5+3,M=1e5+3,inf=1e9;
int n,x,y,z,T,cnt,a[N],Ans[M];char op;
struct QAQ{int l,r,k,op,id;}Q[N+M*2],Q1[N+M*2],Q2[N+M*2];
inline void in(Re &x){
    int fu=0;x=0;char c=getchar();
    while(c<'0'||c>'9')fu|=c=='-',c=getchar();8
    while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c^48),c=getchar();
    x=fu?-x:x;
}
struct BIT{
	int C[N];
	inline void add(Re x,Re v){while(x<=n)C[x]+=v,x+=x&-x;}
	inline int ask_(Re x){Re ans=0;while(x)ans+=C[x],x-=x&-x;return ans;}
	inline int ask(Re l,Re r){return ask_(r)-ask_(l-1);}
}T1;
inline void sakura(Re l,Re r,Re L,Re R){
	if(L>R)return;
	if(l==r){
		for(Re i=L;i<=R;++i)if(Q[i].op>1)Ans[Q[i].id]=l;
		return;
	}
	Re mid=l+r>>1,m1=0,m2=0;
	for(Re i=L;i<=R;++i)
		if(Q[i].op&1){
			if(Q[i].l<=mid)Q1[++m1]=Q[i],T1.add(Q[i].id,Q[i].r);
			else Q2[++m2]=Q[i];
		}
		else{
			Re tmp=T1.ask(Q[i].l,Q[i].r);
			if(Q[i].k<=tmp)Q1[++m1]=Q[i];
			else Q[i].k-=tmp,Q2[++m2]=Q[i];
		}
	for(Re i=1;i<=m1;++i)if(Q1[i].op&1)T1.add(Q1[i].id,-Q1[i].r);
	for(Re i=1;i<=m1;++i)Q[L+i-1]=Q1[i];
	for(Re i=1;i<=m2;++i)Q[L+m1+i-1]=Q2[i];
	sakura(l,mid,L,L+m1-1);
	sakura(mid+1,r,L+m1,R);
}
int main(){
	in(n),in(T);
	for(Re i=1;i<=n;++i)in(a[i]),Q[++cnt]=(QAQ){a[i],1,0,1,i};
	while(T--){
		scanf(" %c",&op),in(x),in(y);
		if(op=='C')Q[++cnt]=(QAQ){a[x],-1,0,1,x},Q[++cnt]=(QAQ){a[x]=y,1,0,1,x};
		else in(z),Q[++cnt]=(QAQ){x,y,z,2,++Ans[0]};
	}
	sakura(-inf,inf,1,cnt);
	for(Re i=1;i<=Ans[0];++i)printf("%d\n",Ans[i]);
}
posted @ 2022-11-30 22:32  spdarkle  阅读(193)  评论(0编辑  收藏  举报