20260427 - 二分 总结

我不会二分。

二分好像不太好吃,那我们就别吃了吧 /ll

二分

二分是什么?简单来说,就是对于一个具有单调性的问题,已知问题答案的区间,用 \(\log\) 级别的时间复杂度定位这个问题的答案。

二分又分为二分查找和二分答案两种。

  • 二分查找通常的形式为在一个固定的区间(序列)中找第一个(最后一个);
  • 而二分答案则一般是对于一个答案具有单调性的问题,二分这个问题的答案并对应去检验答案进而找到答案。

二分题的大体框架:

int l=1,r=n,ans=-1;
//这里的范围也得看题,不一定每次都是 [1,n]
//ans 一般需要给一个初值,可以是 0,可以是 -1,得具体题目具体分析
while(l<=r){
    int mid=(l+r)>>1;
    if(check(mid))ans=mid,l=mid+1;
    else r=mid-1;
    //check 是一个 bool 类型的函数,用于判断当答案为 mid 时是否可行
    //这里的调整具体是 l=mid+1 还是 r=mid-1 得看题目要求
}

例题选讲

B - Buy an Integer

显然整数的价格是具有单调性的,数越大,价格越高,于是就成了一个二分板子题了。

我真说不好这是二分查找还是二分答案。。。

戳这里看代码
#include<bits/stdc++.h>
#define LL long long
#define UInt unsigned int
#define ULL unsigned long long
#define LD long double
#define pii pair<int,int>
#define pLL pair<LL,LL>
#define pDD pair<LD,LD>
#define fr first
#define se second
#define pb push_back
#define isr insert
#define _i128 __int128
using namespace std;
const LL MAX = 1e9;
LL A,B,X,l,r,Ans;
LL read(){
    LL su=0,pp=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')pp=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){su=su*10+ch-'0';ch=getchar();}
    return su*pp;
}
LL d(LL n){
    LL cnt=0;
    while(n)cnt++,n/=10;
    return cnt;
}
LL check(LL n){
    if(A*n+B*d(n)<=X)return 1;
    else return 0;
}
int main(){
    A=read(),B=read(),X=read();
    l=0,r=MAX,Ans=0;
    while(l<=r){
        LL mid=(l+r)>>1;
        if(check(mid))Ans=mid,l=mid+1;
        else r=mid-1;
    }cout<<Ans<<"\n";
    return 0;
}

C - Interesting drink

对 \(x_i\) 从小到大排序后二分查找板子即可。

戳这里看代码
#include<bits/stdc++.h>
#define LL long long
#define UInt unsigned int
#define ULL unsigned long long
#define LD long double
#define pii pair<int,int>
#define pLL pair<LL,LL>
#define pDD pair<LD,LD>
#define fr first
#define se second
#define pb push_back
#define isr insert
#define _i128 __int128
using namespace std;
const int N = 1e5+5;
LL n,a[N],Q;
LL read(){
    LL su=0,pp=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')pp=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){su=su*10+ch-'0';ch=getchar();}
    return su*pp;
}
int Find(LL x){
    int l=1,r=n,res=0;
    while(l<=r){
        int mid=(l+r)>>1;
        if(a[mid]<=x)res=mid,l=mid+1;
        else r=mid-1;
    }return res;
}
int main(){
    n=read();
    for(int i=1;i<=n;i++)a[i]=read();
    sort(a+1,a+n+1);
    Q=read();
    while(Q--){
        LL x=read();
        cout<<Find(x)<<"\n";
    }
    return 0;
}

D - Kth Excluded

可以二分这个第 \(K\) 小的数,但是如何判断当前的 \(mid\) 到底是第几小的数呢?这里就是二分套二分了,对于一个数 \(x\),令 \(cnt_A\) 表示满足 \(A_i \le x\) 的 \(i\) 个数,则 \(x\) 是第 \(x-cnt_A\) 小的数。

时间复杂度有两只 \(\log\),不过题目给了三秒。

戳这里看代码
#include<bits/stdc++.h>
#define LL long long
#define UInt unsigned int
#define ULL unsigned long long
#define LD long double
#define pii pair<int,int>
#define pLL pair<LL,LL>
#define pDD pair<LD,LD>
#define fr first
#define se second
#define pb push_back
#define isr insert
#define _i128 __int128
using namespace std;
const int N = 1e5+5;
const LL MAX = 2e18;
LL n,Q,a[N];
LL read(){
    LL su=0,pp=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')pp=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){su=su*10+ch-'0';ch=getchar();}
    return su*pp;
}
LL Find(LL x){
    int l=1,r=n,res=0;
    while(l<=r){
        int mid=(l+r)>>1;
        if(a[mid]<=x)res=mid,l=mid+1;
        else r=mid-1;
    }return res;
}
bool check(LL x,LL k){
    LL res=x-Find(x);
    return (res>=k);
}
LL solve(LL k){
    LL l=1,r=MAX,res=1;
    while(l<=r){
        LL mid=(l+r)>>1;
        if(check(mid,k))res=mid,r=mid-1;
        else l=mid+1;
    }return res;
}
int main(){
    n=read(),Q=read();
    for(int i=1;i<=n;i++)a[i]=read();
    sort(a+1,a+n+1);
    while(Q--){
        LL k=read();
        cout<<solve(k)<<"\n";
    }
    return 0;
}

E - Maximum Median

二分答案,二分这个中位数。

先对 \(a\) 做一次排序,从小到大排序。以下所说的 \(a\) 序列都是排序后的。

令 \(m = (n+1) \div 2\),则 \(a_m\) 就是这个 \(a\) 的中位数。

我们要使这个中位数达到 \(mid\),则需要让所有 \(a_i(m \le i \le n)\) 都 \(\ge mid\)。前面的?前面的不影响。

你不会问我为什么要改后面的而不是只改 \(a_m\) 自己吧!因为我们需要的是中位数,如果只改 \(a_m\) 自己不就破坏序列单调性了嘛,那 \(a_m\) 肯定就不是中位数了啊。

戳这里看代码
#include<bits/stdc++.h>
#define LL long long
#define UInt unsigned int
#define ULL unsigned long long
#define LD long double
#define pii pair<int,int>
#define pLL pair<LL,LL>
#define pDD pair<LD,LD>
#define fr first
#define se second
#define pb push_back
#define isr insert
#define _i128 __int128
using namespace std;
const int N = 2e5+5;
const LL MAX = 3e9;
LL n,m,k,a[N],l,r,Ans;
LL read(){
    LL su=0,pp=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')pp=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){su=su*10+ch-'0';ch=getchar();}
    return su*pp;
}
bool check(LL x){
    LL cnt=0;
    for(int i=m;i<=n;i++)
        if(a[i]<x)cnt+=x-a[i];
    return (cnt<=k);
}
int main(){
    n=read(),k=read();
    m=(n+1)/2;
    for(int i=1;i<=n;i++)a[i]=read();
    sort(a+1,a+n+1);
    l=0,r=MAX,Ans=0;
    while(l<=r){
        LL mid=(l+r)>>1;
        if(check(mid))Ans=mid,l=mid+1;
        else r=mid-1;
    }cout<<Ans<<"\n";
    return 0;
}

F - Minimum Width

依旧二分答案,二分这个 \(W\)。

怎么判断一个 \(w\) 是否合法?我们挨个枚举每个单词,如果当前这一行剩下的位置足够再显示下当前这个单词,那么就直接续上没必要新开;否则没办法,新开一行,行数增加。

最后判断一下行数是否 \(\le M\) 即可。显然上面那个贪心是最优构造。

戳这里看代码
#include<bits/stdc++.h>
#define LL long long
#define UInt unsigned int
#define ULL unsigned long long
#define LD long double
#define pii pair<int,int>
#define pLL pair<LL,LL>
#define pDD pair<LD,LD>
#define fr first
#define se second
#define pb push_back
#define isr insert
#define _i128 __int128
using namespace std;
const int N = 2e5+5;
const LL MAX = 1e15;
LL n,m,a[N],l,r,Ans,mx;
LL read(){
    LL su=0,pp=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')pp=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){su=su*10+ch-'0';ch=getchar();}
    return su*pp;
}
bool check(LL w){
    LL cnt=1,lst=w+1;
    for(int i=1;i<=n;i++){
        if(lst>a[i])lst-=a[i]+1;
        else ++cnt,lst=w-a[i];
    }return (cnt<=m);
}
int main(){
    n=read(),m=read();
    for(int i=1;i<=n;i++)
        a[i]=read(),mx=max(mx,a[i]);
    l=mx,r=MAX,Ans=MAX;
    while(l<=r){
        LL mid=(l+r)>>1;
        if(check(mid))Ans=mid,r=mid-1;
        else l=mid+1;
    }cout<<Ans<<"\n";
    return 0;
}

G - Hamburgers

我们统计出一个汉堡分别需要 \(h_b\) 片面包、\(h_s\) 根香肠和 \(h_c\) 片奶酪。所谓的汉堡配方的顺序其实和解决这道题没啥关系。

然后我们二分答案,二分做的汉堡个数。怎么判断能否做 \(mid\) 个汉堡呢?我们只需要计算得到 \(h_b \times mid\) 片面包、\(h_s \times mid\) 根香肠和 \(h_c \times mid\) 片奶酪 总共需要花的最小钱数。显然自己有材料就不用买(谁那么傻),自己的不够用了再买。判断一下这个钱数够不够即可。

戳这里看代码
#include<bits/stdc++.h>
#define LL long long
#define UInt unsigned int
#define ULL unsigned long long
#define LD long double
#define pii pair<int,int>
#define pLL pair<LL,LL>
#define pDD pair<LD,LD>
#define fr first
#define se second
#define pb push_back
#define isr insert
#define _i128 __int128
using namespace std;
const LL MAX = 1e15;
string str;
LL yuan[3],jia[3],ned[3],n,l,r,Ans;
LL read(){
    LL su=0,pp=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')pp=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){su=su*10+ch-'0';ch=getchar();}
    return su*pp;
}
bool check(LL cnt){
    LL sum=0;
    for(int i=0;i<3;i++){
        LL hav=ned[i]*cnt;
        if(yuan[i]<hav)
            sum+=(hav-yuan[i])*jia[i];
    }return (sum<=n);
}
int main(){
    cin>>str;
    for(auto ch:str)
        if(ch=='B')ned[0]++;
        else if(ch=='S')ned[1]++;
        else ned[2]++;
    for(int i=0;i<3;i++)yuan[i]=read();
    for(int i=0;i<3;i++)jia[i]=read();
    n=read();
    l=0,r=MAX,Ans=0;
    while(l<=r){
        LL mid=(l+r)>>1;
        if(check(mid))Ans=mid,l=mid+1;
        else r=mid-1;
    }cout<<Ans<<"\n";
    return 0;
}

H - 路标设置

依旧二分答案,二分这个所谓的“空旷指数”。

还是考虑 check 函数怎么写,即如何判断“空旷指数”为 \(mid\) 时是否合法。很简单的啊,我们枚举原先的路标之间的所有空隙,分别计算出需要增加多少个路标才能让“空旷指数”不超过 \(mid\) 即可。

具体地,如果前一个路标在 \(x\),下一个路标在 \(y\)(\(x < y\)),那么这个间隙里需要额外增加的路标个数当然就是

\[\left \lfloor \frac{y-x-1}{mid} \right \rfloor \]

啦,然后算出总和判断有没有超过最多能增加的路标个数即可。

戳这里看代码
#include<bits/stdc++.h>
#define LL long long
#define UInt unsigned int
#define ULL unsigned long long
#define LD long double
#define pii pair<int,int>
#define pLL pair<LL,LL>
#define pDD pair<LD,LD>
#define fr first
#define se second
#define pb push_back
#define isr insert
#define _i128 __int128
using namespace std;
const int N = 1e5+5;
const LL MAX = 1e10;
LL L,n,k,a[N],l,r,Ans;
LL read(){
    LL su=0,pp=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')pp=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){su=su*10+ch-'0';ch=getchar();}
    return su*pp;
}
bool check(LL x){
    LL cnt=0;
    for(int i=1;i<=n;i++)
        cnt+=(a[i]-a[i-1]-1)/x;
    return (cnt<=k);
}
int main(){
    L=read()+1,n=read(),k=read();
    for(int i=1;i<=n;i++)a[i]=read()+1;
    l=1,r=MAX,Ans=MAX;
    while(l<=r){
        LL mid=(l+r)>>1;
        if(check(mid))Ans=mid,r=mid-1;
        else l=mid+1;
    }cout<<Ans<<"\n";
    return 0;
}

I - Seek the Truth

简单题。

最开始这个 \(a\) 显然扔个啥都无所谓,不扔是最好的,我给扔了个 \(1\)。

我们先 I 0 一次。当且仅当 \(k=1\) 时才有 \(f(0) = 0\) 否则都是 \(f(0)=c\)(\(c>0\)),再用一次 Q 1 判断 \(k\) 是 \(1\) 还是 \(2,3\) 中一个。

然后就要按照 \(k\) 分讨啦!

  • \(k=1\):

    • 我们再 I 一个 \(2^n-1\),此时 \(f(2^n-1)\) 一定是 \(c\)。再用 \(n\) 次 Q 二分找到这个 \(c\) 就可以输出了。
  • \(k=2\) 或 \(3\):

    • 此时 \(c\) 已经在集合里了,我们先不在意究竟是或还是异或,先用 \(n\) 次 Q 二分找到这个 \(c\)。
    • 但是找到 \(c\) 不够,我们还是得判断 \(k\)。再 I 一次 \(c\),如果集合里的数字个数没变说明加进去了一个 \(c\)(或),变了说明加进去了一个 \(0\)(异或),就可以判断出 \(k\) 值啦!
戳这里看代码
#include<bits/stdc++.h>
#define LL long long
#define UInt unsigned int
#define ULL unsigned long long
#define LD long double
#define pii pair<int,int>
#define pLL pair<LL,LL>
#define pDD pair<LD,LD>
#define fr first
#define se second
#define pb push_back
#define isr insert
#define _i128 __int128
using namespace std;
LL T,n;
LL read(){
    LL su=0,pp=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')pp=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){su=su*10+ch-'0';ch=getchar();}
    return su*pp;
}
LL Find(){
    LL l=1,r=(1ll<<n)-1,ans=1;
    while(l<=r){
        LL mid=(l+r)>>1;
        cout<<"Q "<<mid<<"\n";
        fflush(stdout);
        LL res;cin>>res;
        if(res>=1)ans=mid,l=mid+1;
        else r=mid-1;
    }return ans;
}
int main(){
    T=read();
    while(T--){
        n=read();

        cout<<"1\n";
        fflush(stdout);
        //给定 a

        cout<<"I 0\n";
        fflush(stdout);
        LL sz;cin>>sz;

        cout<<"Q 1\n";
        fflush(stdout);
        LL cnt1;cin>>cnt1;
        if(cnt1==sz){//是或/异或
            LL C=Find();//找到值
            cout<<"I "<<C<<"\n";//把自己弄进去
            LL sz2;cin>>sz2;
            if(sz2>sz)cout<<"A 3 ";//多了一个 0 说明是异或
            else cout<<"A 2 ";//没变说明是自身
            cout<<C<<"\n";//输出
            fflush(stdout);
            continue;
        }
        else{//底下藏了一个 0
            LL nn=(1ll<<n)-1;
            cout<<"I "<<nn<<"\n";
            fflush(stdout);
            LL sz2;cin>>sz2;
            //把 C 自身弄进去

            LL C=Find();
            cout<<"A 1 "<<C<<"\n";
            fflush(stdout);
            //找到了
            continue;
        }
    }
    return 0;
}

J - New Year's Gifts

我的二分呢?我的二分呢?我的二分呢?我的二分呢?我的二分呢?我的二分呢?我的二分呢?我的二分呢?我的二分呢?我的二分呢?

全场唯一我没用二分做的题,不知道二分在哪,那就别二分了呗。

最开始让 \(k\) 减掉 \(\sum y\)——反正都是要买的先用掉得了。每个朋友的情况可以包装成一个二元组 \((x,z-y)\),表示如果要让这个朋友开心,则要么用掉一个颜值 \(\ge x\) 的盒子要么多花 \(z-y\) 元。

从小到大枚举盒子颜值 \(x\),首先处理所有需要盒子颜值至少为 \(x\) 的朋友(即二元组第一位是 \(x\) 的),将其二元组对应第二个元素 \(z-y\) 塞进一个大根堆优先队列。为啥要塞大根堆?你先别急,我们还有盒子,再处理每个颜值恰好为 \(x\) 的盒子。由于 \(x\) 是从小到大枚举的,所以目前优先队列中每个值都能被这个颜值的盒子应付,为了最大化开心的朋友个数,也就是为了省下尽可能多的钱,盒子肯定用在需要钱数最高的朋友身上啦!

盒子都处理完之后,优先队列里可能还有东西。此时把优先队列反过来变成小根堆,从小到大贪心地看 \(k\) 元能再尽可能让几个朋友开心。

就做完了。我的二分呢?

戳这里看代码
#include<bits/stdc++.h>
#define LL long long
#define UInt unsigned int
#define ULL unsigned long long
#define LD long double
#define pii pair<int,int>
#define pLL pair<LL,LL>
#define pDD pair<LD,LD>
#define fr first
#define se second
#define pb push_back
#define isr insert
#define _i128 __int128
using namespace std;
const int N = 2e5+5;
LL T,n,m,k,a[N],Ans;
vector<LL> p[N];
priority_queue<LL> q;
priority_queue<LL,vector<LL>,greater<LL>> q2;
LL read(){
    LL su=0,pp=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')pp=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){su=su*10+ch-'0';ch=getchar();}
    return su*pp;
}

int main(){
    T=read();
    while(T--){
        n=read(),m=read(),k=read();
        for(int i=1;i<=m;i++)p[i].clear();
        for(int i=1;i<=m;i++)
            a[i]=read(),p[a[i]].pb(0);
        for(int i=1;i<=n;i++){
            LL x=read(),y=read(),z=read();
            p[x].pb(z-y);k-=y;
        }while(!q.empty())q.pop();
        while(!q2.empty())q2.pop();
        Ans=0;
        for(int i=1;i<=m;i++)
            for(int j=p[i].size()-1;j>=0;j--){
                LL x=p[i][j];
                if(!x){if(!q.empty())q.pop(),Ans++;}
                else q.push(x);
            }
        while(!q.empty())
            q2.push(q.top()),q.pop();
        while(!q2.empty()&&q2.top()<=k)
            k-=q2.top(),Ans++,q2.pop();
        cout<<Ans<<"\n";
    }
    return 0;
}

K - Dinner Invitation

看似很难,实则水爆了。

Joler:首先有一个很经典的 trick,就是我们对这些人的预算 \(r_i\) 进行一个降序排序,……

啊??我就是没想到这一步啊!想到了这一步不就做完了吗?这个 trick 经典吗???

好吧,我也不知道,可能是我太菜了。

总之,先对这些人的预算进行一个降序排序,这样我们从前往后枚举的时候,就知道当前的均摊预算至多是当前枚举到的那个人的预算了。

依旧二分,二分这个能去的最大人数。对于一个 \(mid\) 怎么判断呢?维护一个优先队列(大根堆),顺序枚举这些人,首先将当前枚举到的人(假设是 \(i\))的 \(w_i\) 加入优先队列,然后由于我们限制了人数是 \(mid\),所以如果优先队列的大小超过 \(mid\) 就干掉堆顶(由于是大根堆所以堆顶是最大的,显然这样处理也是最优的)。哦对了还需要实时记录当前优先队列中所有数字的总和。如果当前这个优先队列的大小恰好为 \(mid\),则只要 \(sum\) 不超过 \(mid \times r_i\) 那么这个 \(mid\) 就存在合法方案了呢,就可以直接宣告 OK 了。

嗯……然后就做完了呢!时间复杂度是两只 \(\log\) 的,不过过得去。

戳这里看代码
#include<bits/stdc++.h>
#define LL long long
#define UInt unsigned int
#define ULL unsigned long long
#define LD long double
#define pii pair<int,int>
#define pLL pair<LL,LL>
#define pDD pair<LD,LD>
#define fr first
#define se second
#define pb push_back
#define isr insert
#define _i128 __int128
using namespace std;
const int N = 1e5+5;
struct node{LL val,up;}a[N];
LL T,n,l,r,Ans;
priority_queue<LL> q;
LL read(){
    LL su=0,pp=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')pp=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){su=su*10+ch-'0';ch=getchar();}
    return su*pp;
}
bool cmp(node d1,node d2){return d1.up>d2.up;}
bool check(LL cnt){
    LL sum=0;
    while(!q.empty())q.pop();
    for(int i=1;i<=n;i++){
        q.push(a[i].val),sum+=a[i].val;
        while(q.size()>cnt)sum-=q.top(),q.pop();
        if(q.size()==cnt&&sum<=a[i].up*cnt)return 1;
    }return 0;
}
int main(){
    T=read();
    while(T--){
        n=read();
        for(int i=1;i<=n;i++)a[i].val=read();
        for(int i=1;i<=n;i++)a[i].up=read();
        sort(a+1,a+n+1,cmp);
        l=0,r=n,Ans=0;
        while(l<=r){
            LL mid=(l+r)>>1;
            if(check(mid))Ans=mid,l=mid+1;
            else r=mid-1;
        }cout<<Ans<<"\n";
    }
    return 0;
}

L - Nastiness of Segments

挺没意思的一个题,不知道为什么放最后,其难度显然都不如 I J K 啊。。。看过题情况不都能看出来嘛。

显然这个前缀区间 \(\min\) 值是不升的,而题目所谓的 \(d\) 是严格升序的,因此它最多就一个交点。挺诈骗的对吧。

然后就做完了啊?单点修改,区间 \(\min\) 查询,显然就是线段树维护嘛!然后写一个二分就结束了。

时间复杂度是两只 \(\log\) 的,不过题目给了三秒。听说 yxc 巨佬还可以把这个优化成一只 \(\log\) 不过我感觉没啥必要啊。

戳这里看代码
#include<bits/stdc++.h>
#define LL long long
#define UInt unsigned int
#define ULL unsigned long long
#define LD long double
#define pii pair<int,int>
#define pLL pair<LL,LL>
#define pDD pair<LD,LD>
#define fr first
#define se second
#define pb push_back
#define isr insert
#define _i128 __int128
using namespace std;
const int N = 2e5+5;
const int INF = 0x3f3f3f3f;
LL T,n,Q,a[N],mn[N*4];
LL read(){
    LL su=0,pp=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')pp=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){su=su*10+ch-'0';ch=getchar();}
    return su*pp;
}
int ls(int u){return (u<<1);}
int rs(int u){return (u<<1|1);}
void push_up(int u){
    mn[u]=min(mn[ls(u)],mn[rs(u)]);return;
}
void build(int u,int l,int r){
    if(l==r){mn[u]=a[l];return;}
    int mid=(l+r)>>1;
    build(ls(u),l,mid);build(rs(u),mid+1,r);
    push_up(u);return;
}
void change(int u,int l,int r,int LR,int k){
    if(l>LR||LR>r)return;
    if(l==r&&l==LR){mn[u]=k;return;}
    int mid=(l+r)>>1;
    if(LR<=mid)change(ls(u),l,mid,LR,k);
    else change(rs(u),mid+1,r,LR,k);
    push_up(u);return;
}
int ask(int u,int l,int r,int L,int R){
    if(l>R||L>r)return INF;
    if(L<=l&&r<=R)return mn[u];
    int mid=(l+r)>>1,res=INF;
    if(L<=mid)res=min(res,ask(ls(u),l,mid,L,R));
    if(R>mid)res=min(res,ask(rs(u),mid+1,r,L,R));
    return res;
}
int Sol(int L,int R){
    int l=0,r=R-L,res=0;
    while(l<=r){
        int mid=(l+r)>>1;
        int sol=ask(1,1,n,L,L+mid);
        if(sol>=mid)res=mid,l=mid+1;
        else r=mid-1;
    }int jy=ask(1,1,n,L,L+res);
    return (jy==res);
}
int main(){
    T=read();
    while(T--){
        n=read(),Q=read();
        for(int i=1;i<=n;i++)a[i]=read();
        build(1,1,n);
        while(Q--){
            int opt=read();
            if(opt==1){
                int p=read(),k=read();
                change(1,1,n,p,k);
            }else{
                int l=read(),r=read();
                cout<<Sol(l,r)<<"\n";
            }
        }
    }
    return 0;
}

总结

二分是个好东西。

二分查找我不说了,二分答案一般需要:

  • 注意到答案具有单调性。
  • 在给定答案值时能在合理时间内判断这个答案值是否合法或最优。

然后就可以二分了呢!

突然很好奇今天 A 题有几个人老老实实手打二分了……至少我是手打的,但是好像很多巨佬都用的 lower_bound 哎,怎么可以这样呢!

Thanks reading.

posted @ 2026-04-27 19:45  嘎嘎喵  阅读(61)  评论(0)    收藏  举报