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\)),那么这个间隙里需要额外增加的路标个数当然就是
啦,然后算出总和判断有没有超过最多能增加的路标个数即可。
戳这里看代码
#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\) 值啦!
- 此时 \(c\) 已经在集合里了,我们先不在意究竟是或还是异或,先用 \(n\) 次
戳这里看代码
#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.

浙公网安备 33010602011771号