多种二分模板与写不对二分的可能问题
目录
- 三段式快排与归并排序的学习笔记
- 二分写法的思考与万用式二分
笔记
1. 三段式快排与归并排序的学习笔记
标准的快排或使用三段式快排都是不稳定的排序方法,在一些情况下很容易被卡成狗,例如 P1309 [NOIP2011 普及组] 瑞士轮 使用 STL 的 std::sort 会 \(\texttt{TLE}\) 多个点。若使用 stable_sort 换用封装的归并排序便可以获得稳定的时间复杂度。
2. 二分写法的思考与万用式二分
目前我见到的二分主要有两套模板:
while(l<r){
int mid = (l+r)/2;
if(check(mid)) r = mid;
else l = mid+1;
}
while(l<=r){
int mid = (l+r)/2;
if(check(mid)) r = mid-1;
else l = mid+1;
}
upd in 2026/1/7:还是第二套模板好用,感觉更不容易出问题。
对于第一套模板,在二分查找模板题 P2249 【深基13.例1】查找 中,我们是这样写的:
while(r>l){
int mid = (l+r)/2;
if(a[mid]>=t)r = mid;
else l = mid+1;
}
而在二分题 P2678 [NOIP2015 提高组] 跳石头 中,我们是这样写的:
while(l<r){
int mid = (l+r)/2;
if(check(mid) > m) r = mid;
else l = mid+1, ans = mid;
}
return ans;
在前一题中,答案随编号单调不减,而后一题中,所需要移除的石头数随最短跳跃距离单调不减,但前一题我们需要找到尽可能小的编号,后一题我们应使跳跃距离的最小值最大化,这样就产生了分歧:在 mid 得到取等条件时,前一题我们要让右指针左移而偏左,后一题我们要让左指针右移而偏右;而为什么后一题要维护一个 ans 但前一题却不用呢?在前一题由于是偏左答案,最后 \(\texttt{l = r}\) 都为答案,后一题是偏右答案,显然我们知道右指针的 check(r) != m 在最后为了跳出循环,左指针会 +1 使得 \(\texttt{l = r}\) 都不满足 check(),而左指针在变化前才是我们需要的值,因为由题意可知是必然存在解的。
另外,为什么我们需要让 l = mid+1 呢,模拟一下便知道,由于 int 的舍去小数特性,即天然偏左,如果我们不让左指针 +1,在 check(mid)==m 时会陷入死循环,有朋友可能会问:这难道不会丢解吗?
一般地,我们认为 check(x) == m 时我们找到了一个解 \(\texttt{x}\),在前一题中取等后会有 r=x 操作,指针恰好站在解上没有丢解,在后一题中我们有 ans 维护当前(不一定是最优)解,这也是后一题多维护一个 ans 的原因。
当然,\(\texttt{rxz}\) 老师指出,学院派的二分写法会有极大可能写错,而使用万用的如下二分可以规避写错问题,既可以偏左也可以偏右还不用考虑舍去:
while(r - l > 5) {
int mid = (l + r) / 2;
if(check(mid))
r = mid;
else
l = mid;
}
for(int x=l; x<=r; x++)
if(check(x)) {
cout << x << endl;
return;
}
但是万用式二分虽然万用,在遇到一些情况时反而会更复杂:比如 P4343 [SHOI2015] 自动刷题机 这类解的范围很广的情况。
当用传统写法,我们只需要画个图判断一下左右倾向即可。
void lf(){
ll l = 1, r = 1e14;
while(l<r){
ll mid = (l+r)/2;
if(check(mid) <= k) r = mid;
else l = mid+1;
}
if(check(l) == k) cout<<l;
else cout<<-1;
}
void rht(){
ll l = 1, r = 1e14;
ll ans = 0;
while(l<r){
ll mid = (l+r)/2;
if(check(mid) < k) r = mid;
else l = mid+1, ans = mid;
}
if(check(ans) == k) cout<<ans;
}
或者用另一种模板,在此题下更为清晰,或者说更有对称美。
while(l<=r){
mid=(l+r)/2;
if(check(mid)<=k){
r=mid-1;
if(check(mid)==k) ans1=mid;
}
else l=mid+1;
}
while(l<=r){
mid=(l+r)/2;
if(check(mid)>=k){
l=mid+1;
if(check(mid)==k) ans2=mid;
}
else r=mid-1;
}

浙公网安备 33010602011771号