参考课程是我高中信息竞赛邱老师的课程。
完整的板子可以去资源获取,我设置0积分,应该是免费的,如果C收你费或者下不了B站私我。
二分查找(寻找一个数/最大值最小值 必须有序)
只分第一次和最后一次满足。
整数二分

二分满足答案
所有check(p)类的问题都基本满足第一类,只要是递增关系,都是第一个大于T的。

二分一定要分区**"第一次满足"** 和**"最后一次满足"**
比如最后一次满足,说明是找区间右侧的值,要压缩左边界。

比如**第一次满足,**说明是找区间左侧的值,要压缩右边界。

要不你把y>=s y>s y<=s y<s的四种情况都说一下吧

二分法核心
只有两种情况,一种是第一次满足,一种是最后一次满足

这种诡异的模板实际上是 保留法和排除法的混合。
做题应该根据求第一个还是最后一个,然后锁边界/输出。

练习 矿石问题


P1314
https://www.luogu.com.cn/problem/P1314

cpp
#include<bits/stdc++.h>
using namespace std;
int n,m;
long long s;
//提到了区间和 就是满足某个性质的区间和问题
//重量和价值都会影响区间和,因此需要开两个presum前缀和数组,presum一定要对应一个原始数组,因为迭代的时候肯定要用到presum[i]=presum[i-1]+a[i]
long long presumw[2000005];
long long presumv[2000005];
long long w[2000005];
long long v[2000005];
//同时还要添加检验区间
long long intL[2000005],intR[2000005];
void init(){
memset(presumw,0,sizeof(presumw));
memset(presumv,0,sizeof(presumv));
}
long long caly(long long W){
init();
for(int i=1;i<=n;i++){
if(w[i]>=W){
//弄presum
presumw[i]=presumw[i-1]+1;
//注意 这里的式子是01,所以是+1不是+w[i]!!!
presumv[i]=presumv[i-1]+v[i];
}else{//带性质判断的特殊点就在于 不满足性质的时候也要写,因为是迭代式,不写就断了,这里保持上一个状态即可。
presumw[i]=presumw[i-1];
presumv[i]=presumv[i-1];
}
}
//检测M组区间的所有满足性质的值
long long Y=0;
for(int i=1;i<=m;i++){
long long L=intL[i],R=intR[i];
Y+=(presumw[R]-presumw[L-1])*(presumv[R]-presumv[L-1]);
}
return Y;
}
bool check(long long W){
return caly(W)>=s;
}
int find(){
long long L=0,R=1e12;
while(R>L){
long long W=(R+L+1)/2;
if(caly(W)>=s){//最后一个满足y>=s的 最后一个 L=mid
L=W;
}else{
R=W-1;
}
}
// while(R>L){
// long long W=(R+L)/2;
// if(caly(W)<s){//第一个满足y<s的 第一个 R=mid
// R=W;
// }else{
// L=W+1;
// }
// }
//上面输出的是第一个大于s的W 因此应该检查W和W+1的输出
if(abs(caly(L)-s)<abs(caly(L+1)-s)){
cout<<abs(caly(L)-s)<<endl;
return L;
}else{
cout<<abs(caly(L+1)-s)<<endl;
return L+1;
}
}
int main(){
cin>>n>>m>>s;
for(int i=1;i<=n;i++){
cin>>w[i]>>v[i];
}
for(int i=1;i<=m;i++){
cin>>intL[i]>>intR[i];
}
long long result=find();
//cout<<result<<endl;
return 0;
}
模板
见上文。
练习
复印机

//二分变量是时间 T
//判断时间单调性
//确定搜索范围 l r l=0 r=n*min(x,y)
//批准条件check 能在T内完成复印n-1份
二分条件判断:
找到第一个满足if()的,


cpp
while(R>L){
int mid=(L+R)/2;
if(check(mid)){//如果复印完 时间多
R=mid;
}else{
L=mid+1;
}
}
cpp
#include<bits/stdc++.h>
using namespace std;
//二分变量是时间 T
//判断时间单调性
//确定搜索范围 l r l=0 r=n*min(x,y)
//批准条件check 完成复印
int x,n,y;
long long max_t=1e9;
bool check(int T){//这个时间是否能复印完n-1件
long long cnt=T/x+T/y;
return cnt>=(n-1);
}
int find(){
long long L=0,R=max_t;
while(R>L){
int mid=(L+R)/2;
if(check(mid)){//如果复印完 时间多
R=mid;
}else{
L=mid+1;
}
}
return L;
}
int main(){
cin>>n>>x>>y;
//max_t=n*min(x,y);
if(n==1) cout<<0<<endl;
if(n==2){
cout<<min(x,y)<<endl;
}else{
//其他情况只需要复印n-1份,然后有一份直接单独印刷
long long min_t=find();
cout<<min_t+min(x,y)<<endl;
}
return 0;
}
浮点二分(解方程 也需要单调)
方法五步:
①判断搜索变量
②变量是否单调
③const double eps=1e-6 确定误差
④double/int l r 确定搜索范围(可减小)
⑤批准条件check()
一定要注意,是否所有的参与变量都使用double了》
可以预设解的范围缩小



模板
将if(f(mid)*f(r)<=0)换为bool check()函数

用次数截止(查找多少次停止)

练习
截绳子

cpp
#include<bits/stdc++.h>
using namespace std;
//可行性判断 绳子长度是可单调的
//double len=(r-l)/2 变量是绳子的长度
//eps 误差是10e-6
//double l r 查找范围是 1,1e7
//批准条件check() 批准条件是能切出k段这么长的绳子
const double eps=1e-6;
int n,k;
vector<int> rope(1e7);
bool check(double len){
int cnt=0;
for(int x:rope){
if(x>=len){//如果能够切
cnt+=x/len;
}else{
break;
}
}
return cnt>=k;//能切够k段吗
}
double find(){
double l=0,r=1e7;
while(abs(r-l)>eps){
double len=(l+r)/2;
if(check(len)){//如果够切就可以增加长度
l=len;
}else{
r=len;
}
}
return l;
}
int main(){
cin>>n>>k;
for(int i=0;i<n;i++){
cin>>rope[i];
}
sort(rope.rbegin(),rope.rend());
double max_len=find();
cout<<max_len<<endl;
return 0;
}