整体二分
整体二分
思想概述
整体二分算法,全称为基于值域的分治算法,它的思想是这样的:
在某些题目中,我们需要处理很多个询问,而每一个询问都需要二分答案,且
详细的说,整体二分算法便是离线对所有询问一起同时进行二分答案,然后将符合的与不符合的分为两类,分别递归继续二分答案
一般来讲,整体二分算法的模板伪代码长这样:
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);
}
我们通过几道题来感受一下这个算法
例题应用
- 流星
题目描述
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
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,
In the second line there are
In the third line there are
In the fourth line there is a single integer
输出格式
Your program should print
The
题面翻译
Byteotian Interstellar Union
有
这个星球经常会下陨石雨。BIU 已经预测了接下来
BIU 的第
输入格式
第一行是两个数
第二行有
第三行有
第四行有一个数
输出格式
输出 NIE
。
数据范围
思路
先思考,如果仅仅是询问一次该怎么做,很明显可以二分答案,具体步骤是求出在各个时刻共落下了多少陨石雨,稍加判断即可
这个做法也可以稍加更改,变成:对于每一次l=mid
之前,让其所需要的总陨石数量减去
对这个做法进行两次扩展,首先是扩展到整体二分算法,我们对于每一个子任务,都进行求前缀和以及判断,但这样做会出现问题,首先是:前缀和如果在外部计算不宜处理,在内计算
- 读入值,将每一个国家所有的空间站单独开一个
vector
储存 - 整体二分,进入solve函数
- 在solve中有四个参数
,对于在 中的陨石雨所影响的范围在树状数组所维护的差分数组上更改 - 对于每一个在
内的询问,判断是否加上 的流星雨后满足要求,满足的加入 ,不满足的让其 减去 中所增加的值,加入 - 撤销树状数组修改,复制
数组,继续分治求解
#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");
}
}
- 动态区间第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]);
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!