初学算法----二分查找
二分查找不光是查找值;
1.假定一个值判断是否可行,
2.最大值最小化;
3.最小值最大化;
4.最大平均值;
其模型:
求满足f(x)的条件的最大(最小)的x:
1.首先判断f(x)是什么;
2.然后要求满足f(x)的条件的最大(最小)的x,对于任意满足f(x)的x`>=x(x`<=x),可用二分搜索,left=0,right=max;
3.不断二分直到满足条件;
还有一些需要转化为可以用二分查找的题目:
如在1000000个元素的数组中找两个数相加使和为M;
则可以先排序,再枚举元素,然后用NUM=M-array[i],再来找数组中是否有NUM;
----------------------------------------------------------------------------------------------------------
辨析出口条件 left<=right 还是 right-left>1
1.对于 left<=right;
这个是对于 解集的范围在[left,right] 中,设初始值时 left=0,right=size-1(或size) 一定要让left与right一开始就在解集的范围内,并在两端;
因为在[left,right]中,当left==right时还有一个元素要判断.
同时,要清楚二分法也是数学逻辑推导;
如:在数组(size的长度)寻找小于num的最大下标:
若这样设left=0,right=size-1;
mid=left+(right-left)/2;
当 array[mid]>=num,说明解的范围一定在[left,mid-1]中,则right=mid-1;
当 array[mid]<num,说明解的范围一定在[mid,right]中,但不能 left=mid;
如
所以在出口条件为left<=right的情况下,left=mid+1;但还有一个问题是mid可能也是答案,则要一个变量保存它 pos=mid;
这里有一个好博客:https://blog.csdn.net/baipanshi4037/article/details/102047904
对于二分查找我有个大问题:就是为什么这个是返回pos?以及网线管理那道题:
这样是错的:
#include<bits/stdc++.h> using namespace std; #define MAXNUM 10005 int weblen[MAXNUM]; bool funcjudge(int len,int N,int K) { int cnt=0; for (int i=0;i<N;i++) { cnt+=weblen[i]/len; } if (cnt>=K) return true; else return false; } int main() { int N,K; cin>>N>>K; for (int i=0;i<N;i++) { double temp; cin>>temp; weblen[i]=ceil(temp*100);//防止奇奇怪怪的精度问题; } int l=100,r=1e7,mid,temp; while (l<=r) { mid=(l+r)/2; if (funcjudge(mid,N,K)) { temp=mid; l=mid+1; } else { r=mid-1; } } printf ("%.2lf",temp/100.0); return 0; }
但改一改:
这样是对的:
#include<bits/stdc++.h> using namespace std; #define MAXNUM 10005 int weblen[MAXNUM]; bool funcjudge(int len,int N,int K) { int cnt=0; for (int i=0;i<N;i++) { cnt+=weblen[i]/len; } if (cnt>=K) return true; else return false; } int main() { int N,K; cin>>N>>K; for (int i=0;i<N;i++) { double temp; cin>>temp; weblen[i]=ceil(temp*100);//防止奇奇怪怪的精度问题; } int l=0,r=1e7+1,mid,temp; while (r-l>1) { mid=(l+r)/2; if (funcjudge(mid,N,K)) { l=mid; } else { r=mid; } } printf ("%.2lf",l/100.0); return 0; }
2. 对于 right-left>1;
它是 解集 在[left,right) 或(left,right] 中 ,当 right-left=1 或 left==right 时都无元素了;
则 其初始化 left=-1 (left=0),right=size-1(right=size),总之,让一个不在解集里;
还是那个例子如:在数组(size的长度)寻找小于num的最大下标:
left=0,right=size;
当 array[mid]>=num;
解一定在[left,mid)中;
当 array[mid]<num;
解一定在[mid,right)中;
出口条件 right-left<1;
核心: 不断二分 缩小解的范围 直到解集元素为空,无法二分;
//----------------------------------------------------------------------------------------------------------------------------------
高精度的二分:
出口条件: right-left>1e-6(或者更高的精度,但不能太高,会死循环)
用 left=mid与right=mid;
//--------------------------------------------------------------------------------------------------------------------------------
最大值最小化 与 最小值最大化 问题;
有两个例子:1.农夫与奶牛;
2.跳河
河中跳房子
1000ms 65536K
描述:
每年奶牛们都要举办各种特殊版本的跳房子比赛,包括在河里从一个岩石跳到另一个岩石。这项激动人心的活动在一条长长的笔直河道中进行,在起点和离起点L远 (1 ≤ L≤ 1,000,000,000) 的终点处均有一个岩石。在起点和终点之间,有N (0 ≤ N ≤ 50,000) 个岩石,每个岩石与起点的距离分别为Di (0 < Di < L)。
在比赛过程中,奶牛轮流从起点出发,尝试到达终点,每一步只能从一个岩石跳到另一个岩石。当然,实力不济的奶牛是没有办法完成目标的。
农夫约翰为他的奶牛们感到自豪并且年年都观看了这项比赛。但随着时间的推移,看着其他农夫的胆小奶牛们在相距很近的岩石之间缓慢前行,他感到非常厌烦。他计划移走一些岩石,使得从起点到终点的过程中,最短的跳跃距离最长。他可以移走除起点和终点外的至多M (0 ≤ M ≤ N) 个岩石。
请帮助约翰确定移走这些岩石后,最长可能的最短跳跃距离是多少?
输入:
第一行包含三个整数L, N, M,相邻两个整数之间用单个空格隔开。接下来N行,每行一个整数,表示每个岩石与起点的距离。岩石按与起点距离从近到远给出,且不会有两个岩石出现在同一个位置。
输出:
一个整数,最长可能的最短跳跃距离。样例输入:
25 5 2 2 11 14 17 21
样例输出:
4
这里用大佬的好方法:
1 #include<iostream>
2 #define MAXN 50010
3 using namespace std;
4 int a[MAXN];
5 int l, n, m;
6
7 bool find(int mid)
8 {
9 int now = 0, num = 0;
10 for(int i = 1; i <= n + 1; ++ i)
11 {
12 if(a[i] - a[now] >= mid)
13 {
14 ++ num; //num表示剩余石头数,当 a[i]-a[now]时,就一定要踩上那块石头了,既然踩上了,这块石头就一定存在,num++;这里他把河对岸的最后的石头也算上了,
15 now = i;
16 }
17 }
18 if(num >= n - m + 1)//所以这里n-m后还+1;
19 return 1;
20 else
21 return 0;
22 }
23
24 int main()
25 {
26 cin >> l >> n >> m;
27 for(int i = 1; i <= n; ++i)
28 cin >> a[i];
29 int le = 0, mid, ans;
30 a[n+1] = l;
31 int ri = a[n+1];
32 while(le <= ri)
33 {
34 mid = (le + ri)/2;//mid为最长可能的最短跳跃距离
35 if(find(mid))
36 {
37 ans = mid;
38 le = mid + 1;
39 }
40 else
41 ri = mid - 1;
42
43 }
44 cout << ans << endl;
45 return 0;
46 }
当然这道题还可用记录去掉的石头数,但我认为有问题;
//--------------------------------------------------------------------------------------------------------------------------------
月度开销
描述:
农夫约翰是一个精明的会计师。他意识到自己可能没有足够的钱来维持农场的运转了。他计算出并记录下了接下来 N (1 ≤ N ≤ 100,000) 天里每天需要的开销。
约翰打算为连续的M (1 ≤ M ≤ N) 个财政周期创建预算案,他把一个财政周期命名为fajo月。每个fajo月包含一天或连续的多天,每天被恰好包含在一个fajo月里。
约翰的目标是合理安排每个fajo月包含的天数,使得开销最多的fajo月的开销尽可能少。
输入:
第一行包含两个整数N,M,用单个空格隔开。接下来N行,每行包含一个1到10000之间的整数,按顺序给出接下来N天里每天的开销。
输出:
一个整数,即最大月度开销的最小值。#include<cstdio>
#include<cstdlib>
#include<algorithm>
using namespace std;
int n,m,a[100009],maxx=0,tot,ans;
bool check(int);
int main()
{
scanf("%d%d",&n,&m);//输入n天,m个财政周期
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
tot+=a[i];//二分的右端点是总的开销
if(a[i]>maxx)maxx=a[i];//二分的左端点是n天中最大的开销
}
int l=maxx,r=tot,mid;
while(l<=r)//二分查找
{
mid=(l+r)>>1;
if(check(mid))//如果中间值可以
{
ans=mid;//记录答案
r=mid-1;//缩小范围
}
else
l=mid+1;//否则扩大范围
}
printf("%d",ans);//输出最小值
}
bool check(int x)//当最小值是x时是否可以
{
int sum=0,yfen=1;
for(int i=1;i<=n;i++)
{
if(sum+a[i]>x)//sum是一直累加,当超过x时月份++,说明这一天自己另一个月份
{
sum=a[i];
yfen++;
}
else
sum+=a[i];
}
if(yfen<=m)return 1;//月份等于m时正好分成m个月份,小于m时,可以将某些月份中的天数拆开组成新月份,满足分成m个月份
else
return 0;
}

浙公网安备 33010602011771号