二分
这是一个二分算法的总结
对于算法来说,二分的确非常简单,被归类于基础算法,但是同样的,因为是基础算法,所以在竞赛中非常常用,今天距离CSP只剩下不到9天的时间了,我将利用这些时间对我的一些基础算法进行一些理解和总结,让csp得到尽量高的分数
1.狭义二分
当我们第一次见到二分的时候,我们是在一道题:
从一个有序数列中找到某个数的位置,这道题因为数列是有序的满足单调性,单调递增或者单调递减,所以我们可以使用二分算法,核心代码如下:
//a为有序序列的数组
int l=1,r=n;//n是数组的长度
while(l<r){
int mid=(l+r)/2;
if(x<a[mid]) r=mid;//x为要查找的数字
else l=mid+1;;
}
最后l即为最后的答案
通过这个程序,我们知道了通过二分可以来进行对于查找的简化,大大降低时间复杂度,让程序运行更加高效,最主要可以获得更高的分数。
2.广义二分
因此我们从上面的例子可以明白,二分法实际上是对于一个单元函数具有单调性的时候,一个非常好的查找方法,因此我们可以运用这个方法到实际中,比如进行二分答案,快速查找答案
二分答案有以下应用情景:
平衡树查询第k大
最小化最大值
求第k大
二分查找 lower_bound
连续函数中值定理。区间中找到[mn,mx]中任一p的位置。
集合中找出一个数。每次两个集合中至少有一个包含那个数即可
下面一道经典例题,主要讲的是最小值最大的问题,同样这道题也有一些贪心的思想
洛谷P2678 跳石头
题目背景
一年一度的“跳石头”比赛又要开始了!
题目描述
这项比赛将在一条笔直的河道中进行,河道中分布着一些巨大岩石。组委会已经选择好了两块岩石作为比赛起点和终点。在起点和终点之间,有(N)块岩石(不含起点和终点的岩石)。在比赛过程中,选手们将从起点出发,每一步跳向相邻的岩石,直至到达终点。
为了提高比赛难度,组委会计划移走一些岩石,使得选手们在比赛过程中的最短跳跃距离尽可能长。由于预算限制,组委会至多从起点和终点之间移走(M)块岩石(不能移走起点和终点的岩石)。
输入格式
第一行包含三个整数(L,N,M)分别表示起点到终点的距离,起点和终点之间的岩石数,以及组委会至多移走的岩石数。保证 (Lge 1且 Nge Mge 0)。
接下来 (N) 行,每行一个整数,第 (i) 行的整数(D_i (0le D_ile L)), 表示第(i)块岩石与起点的距离。这些岩石按与起点距离从小到大的顺序给出,且不会有两个岩石出现在同一个位置。
输出格式
一个整数,即最短跳跃距离的最大值。
输入输出样例
输入 #1
25 5 2
2
11
14
17
21
输出 #1
4
说明/提示
输入输出样例 1 说明:将与起点距离为 2和 14 的两个岩石移走后,最短的跳跃距离为 4(从与起点距离 17 的岩石跳到距离21的岩石,或者从距离 21 的岩石跳到终点)。
另:对于 20%的数据,(0le Mle Nle10)
对于50%的数据,(0le Mle N le 100)。
对于 100%的数据,(0le M le N le 50,000,1le L le 1,000,000,000)
思路
首先,有n块岩石,然后每个岩石距离起点有一定的距离,并且是有序的,接着分析,我们可以移除m块石头,且不可以移除终点和起点的,目的是使最短跳跃距离的最大值。
也就是说,这道题的目的是把最小距离最大化,因此我们设当移除dis块石头的时候,可以是最小距离最大化,这个最小距离的最大值为x。当当前石头和之前石头的距离小于dis的时候,我们就需要移走一块石头,因为我们设置的最小距离是dis,我们要使这个当前的时候和之前的石头的距离大于dis,这个时候就cnt(我们设置的计数器,用来统计需要移走的石头)加一。如果当当前石头和之前的石头的距离大于dis的时候,我们就不需要移走石头,也就是说这种情况符合题意,然后把之前的石头的距离x替换为当前的石头的距离,用于和下一块石头进行比较。
由于当我们的距离dis越来越大的时候,我们需要移走的石头将会又来越多,所以这个函数符合单调递增的原则,因为我们需要找到一个值小于m,则当m大于cnt的时候就选择左区间进行查找,当m小于cnt的时候就选择右区间进行查找,这样循环下去,就可以找到最小距离的最大值。
代码
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <string>
#include <cstring>
using namespace std;
int l,n,m,ans;
int d[50005];
bool check(int dis)
{
int cnt=0,x=0;
for(int i=1;i<=n+1;i++)
{
if(d[i]-x<dis)
cnt++;
else x=d[i];
}
return cnt<=m;
}
int main()
{
cin>>l>>n>>m;
for(int i=1;i<=n;i++)
cin>>d[i];
d[n+1]=l;
int left=0,right=l;
while(left<=right)
{
int mid=(left+right)/2;
if(check(mid)) left=mid+1,ans=mid;
else right=mid-1;
}
cout<<ans<<endl;
}
这是我目前对于二分的总结,之后将会继续总结,持续更新中