这道题的解法比较巧妙(一开始没有想到)
用了二分搜索来确定解
一开始我的想法是找到一个距离最小的间隔,如果在边上(间隔起点为0 Or 间隔终点为N+1),那么就和唯一相邻的合并,否则的话向两边找,如果左边的比右边的长,
就和右边的合并(提高最小间隔的数值),如果右边的长,相同道理和左边合并;两边一样长时,继续向两侧找;如果一直试到边上两边都一样长,那么随便选一个;
如果在一侧碰到边以前两边都一样长,那就向长的那一边合并
但是这在有多个最小值时无法确定该破坏哪个间隔,问题会越来越复杂
看了网上二分搜索+greedy的例子,觉得这种方法解题的角度和我之前的想法是完全不同的!
参考的解法是这样做的:
首先,二分间隔长度,计算出使得最小间隔<当前间隔长度时,去掉的最少石头是多少;
假设有两个间隔d1,d2, d1 > d2,对应的石头数分别为n1,n2,那么n1 <= n2(因为去掉n2个已经能满足最小间隔<d2,那么自然最小间隔<d1)
反过来考虑,给定可以去掉的石头总数时,这种方法可以得到的最小间隔是最大的;
那么怎样确定这个需要去掉的最小的石头数呢?
这里用到了greedy algorithm,即从第一个间隔开始加,在每个当前长度内放尽可能多的间隔
证明:
对于当前的给定间隔长度,假设存在最优解(去掉的石头最少)使得a11,...,a1i1个间隔在第一个段内, ak1...ak ik在第k个间隔内,
且a21可以放入第一个段内使得第一段的长度不超过当前长度,那么把它从第二段拿到第一段,所得到的仍然是最优解,(因为第二个长度变短了,而已知第一个不超过当前长度),可以得到另一组最优解;
这也是greedy的一种常用证明方法
代码如下:
#include<iostream>
#include<string.h>
#include<cstring>
#include<string>
#include<cstdio>
#include<algorithm>
using namespace std;
const int maxn = 50050;
int l,m,n;
int dist[maxn];
int binarySearch(int DeleteNum,int,int maxlen);
int main(){
int i,j;
while(scanf("%d%d%d",&l,&n,&m)!=EOF){
memset(dist,0,sizeof(dist));
dist[0] = 0;
dist[n+1] = l;
for(i = 1; i <= n; ++i){
scanf("%d",dist+i);
}
int rocknum = n + 2;
sort(dist,dist + rocknum);
int lowerbound = l;
for(i = 1; i < rocknum; ++i){
lowerbound = min(lowerbound,dist[i] - dist[i-1]);
}
//printf("%d\n",lowerbound);
printf("%d\n",binarySearch(m,lowerbound,l));
}
return 0;
}
int binarySearch(int todelete,int lowerbound,int totaldist){
// printf("to delete: %d\n",todelete);
int minlen = lowerbound, maxlen = totaldist,midlen,i,j,rocknum = n + 2;
while(minlen <= maxlen){
midlen = (minlen + maxlen) >> 1;
int cntdelete = 0;
//globalvariable l为总长度,m为deletestone数,n为中间石头数
int s = 0, e = 1,rocknum = n + 2;
while(e < rocknum){
if(dist[e] - dist[s] >= midlen){
s = e; ++e;
}
else{
++e; ++cntdelete;
}
}
if(cntdelete > todelete){
maxlen = midlen - 1;
}
else{
minlen = midlen + 1;
}
}
return maxlen;
}
通过这道题认真复习了一下二分法~对于二分的取等条件和正确性也有了更深入的思考~
btw:开始二分部分代码写错了,应该是根据结果修改上下界而非midlen,因为这个值每次循环都要重新计算~