二分查找&二分答案,详细讲解博客地址:https://blog.csdn.net/Mr_dimple/article/details/114656142
二分查找:
1、数的范围:
#include<iostream>
using namespace std;
const int N=100010;
int a[N],q,x,n;
int main(){
cin>>n>>q;
for(int i=0;i<n;i++) cin>>a[i];
while(q--)
{
cin>>x;
int l=0,r=n-1;
while(l<r) //找最左边的边界 注意没有 "="
{
int mid=l+r>>1;
if(a[mid]>=x) r=mid; //判断条件:在右边的话就往左来
else l=mid+1;
}
if(a[l]!=x){
cout<<"-1 -1"<<endl;
continue;
}
cout<<l<<" ";
l=0,r=n-1;
while(l<r) //找最右边的边界
{
int mid=l+r+1>>1;
if(a[mid]<=x) l=mid; //在左边的话就往右来
else r=mid-1;
}
cout<<l<<endl;
}
return 0;
}
2、砍树:
#include<iostream>
using namespace std;
const int N=1e6+10;
long long a[N],n,m,maxa,sum;
int main(){
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>a[i];
if(a[i]>maxa) maxa=a[i]; //找一下可能的最大高度
}
long long l=0,r=maxa;
while(l<r) //查找最高的满足情况的高度
{
long long mid=l+r+1>>1;
sum=0;
for(int i=1;i<=n;i++){
if(a[i]>mid) sum+=a[i]-mid;
}
if(sum>=m) l=mid; //低了
else r=mid-1;
}
cout<<l<<endl; //一定有解
return 0;
}
3、数的三次方根:
#include<iostream>
using namespace std;
double n;
int flag=1;
int main(){
cin>>n;
if(n<0) flag=-1,n=-n; //负数特判
double l=0,r=n;
if(n<1) r=1; //小数特判,因为小数乘小数越乘越小,而小数的三次根是比自身大的,故无法解小数的三次根
//又考虑到小数的三次根肯定小于1,则将r设为1
while(r-l>1e-8)
{
double mid=(l+r)/2;
if(mid*mid*mid>=n) r=mid;
else l=mid;
}
printf("%.6f",l*flag);
return 0;
}
4、一元三次方程求解:
#include<iostream>
using namespace std;
double a,b,c,d;
int search_1(double l,double r)
{
while(r-l>1e-5)
{
double mid=(l+r)/2;
if(a*mid*mid*mid+b*mid*mid+c*mid+d<=0) l=mid;
else r=mid;
}
printf("%.2f ",l);
}
int search_2(double l,double r)
{
while(r-l>1e-5)
{
double mid=(l+r)/2;
if(a*mid*mid*mid+b*mid*mid+c*mid+d>=0) l=mid;
else r=mid;
}
printf("%.2f ",l);
}
int main(){
cin>>a>>b>>c>>d;
//注意根与根之差的绝对值>=1,故可以遍历每个点,看是否根在这个点附近,如果在的话,二分查找
for(double i=-101;i<=101;i++){
if(a*i*i*i+b*i*i+c*i+d==0){
printf("%.2f ",i);
}
else if(a*i*i*i+b*i*i+c*i+d<0&&a*(i+1)*(i+1)*(i+1)+b*(i+1)*(i+1)+c*(i+1)+d>0){
search_1(i,i+1);
}
else if(a*i*i*i+b*i*i+c*i+d>0&&a*(i+1)*(i+1)*(i+1)+b*(i+1)*(i+1)+c*(i+1)+d<0){
search_2(i,i+1);
}
}
return 0;
}
二分答案:
1、进击的奶牛:
#include<iostream>
#include<algorithm>
using namespace std;
const int N=100010;
int a[N],n,m,maxa;
int check(int mid)
{
int i=1,j=1,cnt=0; //第一个格子必放牛,才能使后面的间距尽可能大,从而保证最短距离mid最大化
while(j<n){
j++;
if(a[j]-a[i]>=mid){ //保证间距大于等于最短距离mid,才能把牛放进去
cnt++,i=j;
}
}
if(cnt+1>=m) return 1; //放进的牛个数大于m,都能保证间距大于最短距离mid,那放进去m个,肯定还是大于最短距离mid
return 0;
}
int main(){
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>a[i];
if(a[i]>maxa) maxa=a[i];
}
sort(a+1,a+n+1); //排序勿忘
int l=0,r=maxa;
while(l<r)
{
int mid=l+r+1>>1;
if(check(mid)) l=mid;
else r=mid-1;
}
cout<<l;
return 0;
}
2、路标设置:
#include<iostream>
using namespace std;
const int N=100010;
long long a[N],n,m,len,x;
int check(int mid)
{
int cnt=0,ans=0;
for(int i=1;i<=len;i++){
cnt++;
if(a[i]==1){
cnt=0;
continue;
}
if(cnt+1>mid) ans++,cnt=0; //如果两路标间距大于最大距离mid了,即,不能保证" 区间距离小于最大距离mid或相等 ",那就增设
}
if(ans<=m) return 1; //增设不到m个就能满足 "间距小于最大距离或相等" ,那增设m个肯定也满足
return 0;
}
int main(){
cin>>len>>n>>m;
for(int i=1;i<=n;i++){
cin>>x;
a[x]=1;
}
a[0]=1,a[len]=1;
int l=0,r=len;
while(l<r)
{
int mid=l+r>>1;
if(check(mid)) r=mid;
else l=mid+1;
}
cout<<l;
return 0;
}
3、最佳牛围栏 :
#include<iostream>
using namespace std;
const int N=100010;
int a[N],n,m;
double sum[N];
int check(double mid)
{
//难点:如何判断一个长度大于等于m的区间的平均值是否大于mid。
//求平均值转化:可将区间的每个值减掉mid,判断该区间的和是否大于等于0,如果是,那么平均值就大于等于mid
for(int i=1;i<=n;i++){ //每个值减掉mid(平均值)后取前缀
sum[i]=sum[i-1]+a[i]-mid;
}
//难点:是否需要遍历每一个长度大于等于m的区间
//技巧:取区间的左边界最小的前缀和,用右边界减最小左边界判断是否大于等于0,这样就能找到区间长度大于等于m且最大的区间平均值了
double mina=1e5;
for(int i=0,j=m;j<=n;i++,j++){ //保证区间长度为m,依次往后移
if(sum[i]<mina) mina=sum[i];
if(sum[j]-mina>=0) return 1; //如果该右边界减最小左边界(即区间和)大于等于0,说明区间平均值大于mid
}
return 0;
}
int main(){
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>a[i];
}
double l=0,r=2000;
while(r-l>1e-5)
{
double mid=(l+r)/2;
if(check(mid)) l=mid;
else r=mid;
}
printf("%d",int(r*1000)); //最终输出的是r,而不是l,因为题目让求的是最大值。这一点和整数二分不同!
return 0;
}
4、kotori的设备
代更。。。
有哪里不懂的欢迎留言讨论。
有问题的话还请大佬们不吝赐教。