洛谷P2249做题笔记 二分模板
这一篇我主要讲讲大佬的二分思想。
代码如下:
#include<cstdio>
#include<cstring>
#include<cmath>
#include<iostream>
#include<algorithm>
using namespace std;
int n,num[1000006],m,k;
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)scanf("%d",&num[i]);
while(m--)
{
int l=1,r=n,ans=-1;
scanf("%d",&k);
while(l<=r)
{
int mid=(l+r)/2;
if(num[mid]==k)
{
if(ans==-1)ans=mid;
else ans=min(ans,mid);
}
if(num[mid]<k){l=mid+1;}
else {r=mid-1;}
}
printf("%d ",ans);
}
return 0;
}
1.1
首先应该读题细致,题目给出案列:
11 3
1 3 3 3 5 7 9 11 13 15 15
1 3 6
不难发现,如果存在重复的应该输出最小的位置,这时候有意思的就来了,我对二分的理解是每次取半,但遇到符合项就输出,结束循环。这样做无法保证遇到重复的项时取最小位置。而大佬的题解时真的妙,下面我细品。
1.2
if(num[mid]==k)
{
if(ans==-1)ans=mid;
else ans=min(ans,mid);
}
这是这段代码的核心,如果要找出位置最小的,那么把思维简化点,这不就是要遍历全部数据一次吗?
所以大佬这样写的目的就是:把每次符合题意的点的位置记录下来,如果有多个位置满足题意,就取最小位置的那个点。而且在实现这个功能的前提下,大佬还满足了题目要求:当出现未出现的数时就输出‘-1’。奇妙的想法总是可以极大的化简步骤,这也是学习的方向。
再品味
if(num[mid]<k){l=mid+1;}
else {r=mid-1;}
这段代码的核心目的就是保证能使结果位置最小。
你看,如果当s[mid]<k或者s[mid]>k的话,没话说,直接进行二分。
但如果s[mid]==k时,再向左进行二分,这就可以保证如果左边有位置小的相同数,就可以取到最小。
1.3
还有一个比较好的点就是其判断终止的条件
一开始我写的判断终止的条件是 r-l=1当时退一步越想越不对。发现我想的太窄了。
看大佬写代码,恍然大悟,原来可以这样操作,芜湖。
初解我是打算用递归函数,但是没有成功,等下继续尝试,成功会发文。