二分答案是一个非常优秀的算法,对于一个答案具有单调性的性质,一般都可以用二分来快速解答
但是如果有多个询问呢?
如果每一个询问都是一样的,且具有单调性,而且题目不要求强制在线,就可以用整体二分来求解(其实如果能够用q次二分答案的估计就可以整体二分)
整体二分的具体思路
先将所有的问题离线下来,将问题和答案一起进行二分
将答案的范围找一个中间值mid1,然后再在问题里面一个个比较
如果发现答案比这个大,那么就把这个放到后面的位置
如果发现答案比这个小,那么就把这个放到前面的位置
这样最后这个区间就变成了一个以mid2为中心分开
mid2的左边是关于mid1可行的,右边是不可行的
然后分开递归
最后如果答案范围只有1的空间了,那么就可以记录了
需要注意的是,在整个递归里面,不能超过O(n)级别,不然…(不会算时间复杂度)
所以再整体二分里面,可以套用一些数据结构(线段树,树状数组)用来达到能减少复杂度的目的
题目传送门
-
如果只有一个询问,那么很显然,可以利用二分答案来求解,只要记录小于mid的个数,就可以判断了
-
但是有多个询问,考虑整体二分,但是如何高效的处理每一个问题
-
非常简单的能够想到可以通过一个二维数组a[i][j]表示到(i,j)的大于mid的数,那么只要O(n^2)处理,O(R-L+1)处理询问就行了
-
但是这样还是不够,
万一有个每个问题都均匀分布呢 -
看到处理询问的时间很短,但是预处理很长,所以可以通过结构体来储存每一个的下标和数字,然后按数字从小到大排序,每一次只要计算(l~mid)就行了
-
为什么?考虑一下整体二分的性质,首先对于在mid2左边的一些数,我们目前计算的对它并没有什么影响,所以先往下递归,然后对于在mid2右边的数,这些计算是对它有影响的,所以只要用当前的个数k减去消除的个数就能够排除影响了,这样每一个数只会被统计一次
-
时间复杂度O( ( ( ( N 2 N^2 N2 + Q ) +Q) +Q) l o g 3 N log^3N log3N )(我也不知道怎么算)
#include<bits/stdc++.h>
using namespace std;
const int N=6e4+10;
const int M=510;
struct cow{int x1,y1,x2,y2,k,id;}Q[N],_l[N],_r[N];
struct COW{int x,y,z;}a[M*M];
int read()
{
int num=0;bool flag=1;
char c=getchar();
for(;c<'0'||c>'9';c=getchar())
if(c=='-')flag=0;
for(;c>='0'&&c<='9';c=getchar())
num=(num<<1)+(num<<3)+c-'0';
return flag?num:-num;
}
int n,q,Ans[N],c[M][M];
#define lowbit(x) (x&-x)
int getsum(int x,int y)
{
int sum=0;
for(;x;x-=lowbit(x))
for(int i=y;i;i-=lowbit(i))
sum+=c[x][i];
return sum;
}
void add(int x,int y,int z)
{
for(;x<=n;x+=lowbit(x))
for(int i=y;i<=n;i+=lowbit(i))
c[x][i]+=z;
}
void solve(int L,int R,int l,int r)
{
if(l==r){
for(int i=L;i<=R;i++)
Ans[Q[i].id]=a[l].z;
return ;
}
int mid=l+r>>1,l_=0,r_=0;
for(int i=l;i<=mid;i++)add(a[i].x,a[i].y,1);
for(int i=L;i<=R;i++)
{
int ans=getsum(Q[i].x2,Q[i].y2)-getsum(Q[i].x2,Q[i].y1-1)
-getsum(Q[i].x1-1,Q[i].y2)+getsum(Q[i].x1-1,Q[i].y1-1);
if(ans>=Q[i].k)_l[++l_]=Q[i];
else _r[++r_]=Q[i],_r[r_].k-=ans;
}
for(int i=l;i<=mid;i++)add(a[i].x,a[i].y,-1);
for(int i=1;i<=l_;i++)Q[i+L-1]=_l[i];
for(int i=1;i<=r_;i++)Q[L+l_+i-1]=_r[i];
solve(L,L+l_-1,l,mid);solve(R-r_+1,R,mid+1,r);
}
bool cmp(COW a,COW b){return a.z<b.z;}
int main()
{
n=read();q=read();
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
a[(i-1)*n+j]=(COW){i,j,read()};
sort(a+1,a+n*n+1,cmp);
for(int i=1;i<=q;i++)
{
Q[i].x1=read();Q[i].y1=read();
Q[i].x2=read();Q[i].y2=read();
Q[i].k=read();Q[i].id=i;
}
solve(1,q,1,n*n);
for(int i=1;i<=q;i++)
printf("%d\n",Ans[i]);
return 0;
}
题目传送门
-
这题一眼看下去,咋这么像线段树模板呢 -
首先思考只有一种的情况,那么很显然,二分k的位置,然后再用树状数组在里面维护,做到O( n l o g 2 n nlog^2n nlog2n)
-
但是有多个询问,因为这个也是具有单调性的,考虑整体二分
-
和上题一模一样,只要用树状数组维护一下区间和,然后左边的不变,右边的减一下,递归下去就行了
-
简直一模一样
#include<bits/stdc++.h>
using namespace std;
const int N=3e5+10;
#define ll long long
#define lowbit(x) (x&-x)
int n,m,k,Ans[N],now;ll c[N<<1];vector<int>E[N];
struct cow{int id;ll x;}q[N],_l[N],_r[N];
struct COW{int x,y;ll z;}e[N];
void add(int x,ll z){
for(;x<=2*m;x+=lowbit(x))
c[x]+=z;
}
ll getsum(int x){
ll sum=0;
for(;x;x-=lowbit(x))
sum+=c[x];
return sum;
}
void solve(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>>1,l_=0,r_=0;
for(int i=l;i<=mid;i++)add(e[i].x,e[i].z),add(e[i].y+1,-e[i].z);
for(int i=L;i<=R;i++)
{
ll sum=0;
for(int j=0;j<E[q[i].id].size()&&sum<=q[i].x;j++)
sum+=getsum(E[q[i].id][j])+getsum(E[q[i].id][j]+m);
if(sum>=q[i].x)_l[++l_]=q[i];
else _r[++r_]=q[i],_r[r_].x-=sum;
}
for(int i=l;i<=mid;i++)add(e[i].x,-e[i].z),add(e[i].y+1,e[i].z);
for(int i=1;i<=l_;i++)q[L+i-1]=_l[i];
for(int i=1;i<=r_;i++)q[L+l_+i-1]=_r[i];
solve(L,L+l_-1,l,mid);solve(R-r_+1,R,mid+1,r);
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1,o;i<=m;i++)scanf("%d",&o),E[o].push_back(i);
for(int i=1;i<=n;i++)scanf("%lld",&q[i].x),q[i].id=i;
scanf("%d",&k);
for(int i=1;i<=k;i++)scanf("%d%d%lld",&e[i].x,&e[i].y,&e[i].z);
for(int i=1;i<=k;i++)if(e[i].y<e[i].x)e[i].y+=m;
solve(1,n,1,k+1);
for(int i=1;i<=n;i++)
if(Ans[i]==k+1)printf("NIE\n");
else printf("%d\n",Ans[i]);
return 0;
}
题目传送门
-------------------------------------------------------------------------------------------------
-
那么考虑整体二分
-
有很多的问题
-
前面两道题目并没有修改的操作,而在这一题有
这不是废话吗,还有的就是顺序的问题 -
那么考虑对于这两个询问怎么处理,首先同理一样,对于询问的操作,跟上面一样判断就行(
就是加一个线段树) -
对于添加的操作,假设添加一个数C,当前的二分l,r的中间值是mid
-
那么假如C<=mid,那么它对于右边的区间是没有影响的(
显然),那么只要加入左区间就行了,相反则加入右区间 -
那么就这么简洁愉快的解决了
#include<bits/stdc++.h>
using namespace std;
const int N=5e4+10;
#define ll long long
ll read()
{
ll num=0;bool flag=1;
char c=getchar();
for(;c<'0'||c>'9';c=getchar())
if(c=='-')flag=0;
for(;c>='0'&&c<='9';c=getchar())
num=(num<<1)+(num<<3)+c-'0';
return flag?num:-num;
}
struct Tree
{
struct cow{
int l,r;ll sum,lazy;
#define l(p) tree[p].l
#define r(p) tree[p].r
#define sum(p) tree[p].sum
#define lazy(p) tree[p].lazy
}tree[N<<2];
void build(int p,int l,int r){
l(p)=l;r(p)=r;
if(l==r)return ;
int mid=l+r>>1;
build(p<<1,l,mid);
build(p<<1|1,mid+1,r);
}
void lazytime(int p){
sum(p<<1)+=(r(p<<1)-l(p<<1)+1)*lazy(p);
sum(p<<1|1)+=(r(p<<1|1)-l(p<<1|1)+1)*lazy(p);
lazy(p<<1)+=lazy(p);lazy(p<<1|1)+=lazy(p);lazy(p)=0;
}
ll ask(int p,int l,int r){
if(l(p)>=l&&r(p)<=r)return sum(p);
lazytime(p);ll ans=0;
int mid=l(p)+r(p)>>1;
if(l<=mid)ans+=ask(p<<1,l,r);
if(r>mid)ans+=ask(p<<1|1,l,r);
return ans;
}
void change(int p,int l,int r,int c){
if(l(p)>=l&&r(p)<=r){
sum(p)+=(r(p)-l(p)+1)*c;
lazy(p)+=c;return ;
}
lazytime(p);int mid=l(p)+r(p)>>1;
if(l<=mid)change(p<<1,l,r,c);
if(r>mid)change(p<<1|1,l,r,c);
sum(p)=sum(p<<1)+sum(p<<1|1);
}
}Tree;
struct cow{int opt,l,r,id;ll c;}q[N],_l[N],_r[N];
int n,m,tot;ll Ans[N];
void solve(int l,int r,int L,int R)
{
if(l==r){
for(int i=L;i<=R;i++)
if(q[i].opt==2)Ans[q[i].id]=l;
return ;
}
int mid=l+r>>1,l_=0,r_=0;bool LL=0,RR=0;
for(int i=L;i<=R;i++)
{
if(q[i].opt==1){
if(q[i].c<=mid)_l[++l_]=q[i];
else Tree.change(1,q[i].l,q[i].r,1),_r[++r_]=q[i];
}
else{
ll sum=Tree.ask(1,q[i].l,q[i].r);
if(sum<q[i].c)q[i].c-=sum,_l[++l_]=q[i],LL=1;
else _r[++r_]=q[i],RR=1;
}
}
for(int i=1;i<=l_;i++)q[L+i-1]=_l[i];
for(int i=1;i<=r_;i++){
q[L+l_+i-1]=_r[i];
if(_r[i].opt==2)continue;
Tree.change(1,_r[i].l,_r[i].r,-1);
}
if(LL)solve(l,mid,L,L+l_-1);
if(RR)solve(mid+1,r,L+l_,R);
}
signed main()
{
n=read();m=read();
for(int i=1;i<=m;i++){
q[i].opt=read();
q[i].l=read();q[i].r=read();
q[i].c=read();q[i].id=i;
}Tree.build(1,1,n);
solve(-n,n,1,m);
for(int i=1;i<=m;i++)
if(Ans[i])printf("%lld\n",Ans[i]);
return 0;
}
题目传送门
-
最后亿题了 -
首先想想只有一个的时候怎么二分,显然二分美味度,再对符合美味度的按照价格从小到大进行排序,然后从小到大选。时间复杂度:O( n l o g 2 n nlog^2n nlog2n)(主要的瓶颈在于需要排序)
-
那么考虑整体二分
-
同样是通过mid判断左右,但是主要的问题是如何快速的判断是否可行
-
我们可以通过以价格为下标维护树状数组
-
让后我们只需要将回溯的位置变一下(放在一个l~mid的递归之后)
-
就能轻松解决这个问题了
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define lowbit(x) (x&-x)
ll read()
{
ll num=0;bool flag=1;
char c=getchar();
for(;c<'0'||c>'9';c=getchar())
if(c=='-')flag=0;
for(;c>='0'&&c<='9';c=getchar())
num=(num<<1)+(num<<3)+c-'0';
return flag?num:-num;
}
const int N=1e5+10;
struct cow{int d,p,l;}x[N];
struct COW{ll g,l;int id;}q[N],_l[N],_r[N];
int n,m,Ans[N];ll cl[N],cp[N];
bool cmp(cow a,cow b){return a.d<b.d;}
void add(int p,int l,int z){
for(int i=p;i<=N-10;i+=lowbit(i))
cl[i]+=z*l,cp[i]+=1ll*z*l*p;
}
ll getsum_l(int x){
ll sum=0;
for(;x;x-=lowbit(x))
sum+=cl[x];
return sum;
}
ll getsum_p(int x){
ll sum=0;
for(;x;x-=lowbit(x))
sum+=cp[x];
return sum;
}
int dbl(int l,int r,ll val)
{
while(l<r){
int mid=l+r>>1;
if(getsum_l(mid)>=val)r=mid;
else l=mid+1;
}
return l;
}
void solve(int L,int R,int l,int r)
{
if(l==r){
for(int i=L;i<=R;i++)
Ans[q[i].id]=x[l].d;
return ;
}
int mid=l+r+1>>1,l_=0,r_=0;
//分成l~mid-1和mid~r两段区间
//因为并不知道如果刚好等于的话是在左区间还是右区间
//(有可能后面还有更优的)
for(int i=mid;i<=r;i++)
add(x[i].p,x[i].l,1);
for(int i=L;i<=R;i++)
{
int cnt=dbl(0,N-10,q[i].l);
ll ans_l=getsum_l(cnt),ans_p=getsum_p(cnt);
if(ans_l>=q[i].l&&q[i].g>=ans_p-(ans_l-q[i].l)*cnt)
_r[++r_]=q[i];else _l[++l_]=q[i];
}
for(int i=1;i<=l_;i++)q[i+L-1]=_l[i];
for(int i=1;i<=r_;i++)q[L+l_+i-1]=_r[i];
solve(L,L+l_-1,l,mid-1);
for(int i=mid;i<=r;i++)
add(x[i].p,x[i].l,-1);
solve(R-r_+1,R,mid,r);
}
int main()
{
n=read();m=read();
for(int i=1;i<=n;i++)
{
x[i].d=read();
x[i].p=read();
x[i].l=read();
}x[0]=(cow){-1,1,1};
sort(x,x+n+1,cmp);
for(int i=1;i<=m;i++)
{
q[i].g=read();
q[i].l=read();
q[i].id=i;
}
solve(1,m,0,n);
for(int i=1;i<=m;i++)
printf("%d\n",Ans[i]);
return 0;
}