二分总结

二分

这是一个二分算法的总结

对于算法来说,二分的确非常简单,被归类于基础算法,但是同样的,因为是基础算法,所以在竞赛中非常常用,今天距离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\)分别表示起点到终点的距离,起点和终点之间的岩石数,以及组委会至多移走的岩石数。保证 \(L\ge 1且 N\ge M\ge 0\)

接下来 \(N\) 行,每行一个整数,第 \(i\) 行的整数\(D_i (0\le D_i\le L)\), 表示第\(i\)块岩石与起点的距离。这些岩石按与起点距离从小到大的顺序给出,且不会有两个岩石出现在同一个位置。

输出格式

一个整数,即最短跳跃距离的最大值。

输入输出样例

输入 #1

25 5 2 
2
11
14
17 
21

输出 #1

4

说明/提示

输入输出样例 1 说明:将与起点距离为 2和 14 的两个岩石移走后,最短的跳跃距离为 4(从与起点距离 17 的岩石跳到距离21的岩石,或者从距离 21 的岩石跳到终点)。

另:对于 20%的数据,\(0\le M\le N\le10\)

对于50%的数据,\(0\le M\le N \le 100\)

对于 100%的数据,\(0\le M \le N \le 50,000,1\le 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;
}

这是我目前对于二分的总结,之后将会继续总结,持续更新中

posted @ 2020-10-27 19:43  wweiyi  阅读(138)  评论(0编辑  收藏  举报
js脚本