luogu2678 跳石子 二分
题目大意:
一个赛道上除起点、终点外有\(N\)个点。现要求你从中删除\(M\)个点,使得剩余点序列中相邻的点的最小值最大。求这个最大的最小值。
思路
我们最容易想到的算法便是:对序列从头到尾循环\(M\)次,每次把间距最小的一对点删除其中一个,删除的那一个点到两边的点的距离之和应当是相对小的那一个。但是代码具体实现怎么做?而且时间复杂度也太高了!此时我们应当直接换切入点,而不是想方设法在这个思路上寻找解决方法。
题中要我们求的是值,而值的范围由起点和终点不改变已经是有限了。而且显然要求的这个最小值越大,要删去的点不变或越多,否则不变越少。于是我们就可以对要求的最大的最小值进行UpperBound二分了。
那么对于一个潜在的值mid,如何得知要想使剩余点序列中相邻的点的最小值至少为\(m\)要删去多少个点呢?问题就在于,如果我们知道一对相邻点距离小于mid,我们是删除左面的点还是右面的点。贪心告诉我们删除右面的点,因为删除右边的点r会影响到更右边的点r',它若不删去r则它与相邻的点的距离小于mid,那么它更可能不用删去它本身而满足条件,而删除左面的点达不到这样的效果。
//#define _DEBUG//DELETE!!!!!!!!!!!!!!!
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int MAX_P = 50010;
int P[MAX_P];
int N, M, L;
bool CanDelInM(int x)
{
int lastPos = 0, cnt = 0;
for(int i=1; i<=N+1; i++)
{
if(P[i] - lastPos < x)
cnt++;
else
lastPos = P[i];
}
return cnt <= M;
}
int UpperBound(int l, int r, bool (*GreaterOrEqual)(int))
{
while(r > l)
{
int mid = (l + r + 1) / 2;
if(GreaterOrEqual(mid))
l = mid;
else
r = mid - 1;
}
return l;
}
int main()
{
scanf("%d%d%d", &L, &N, &M);
for(int i=1; i<=N; i++)
scanf("%d", P + i);
P[N+1]=L;
sort(P+1, P+N+1);
printf("%d\n", UpperBound(1, L, CanDelInM));
return 0;
}