River Hopscotch POJ - 3258【贪心、二分】

(题目是英文的,好烦,我直接讲一下题意了)题目连接    

    题意: 题目给你一个L,L表示的是这段路的长度,即[0, L],接下来一个N表示在这条路径(除两头0和L)上有N块石头,接下来给定一个M,代表可以去除M块石头,我们要做的就是求出去掉M块石头后的两两石头间距离最小值的最大值。

    对于这样的题,一开始真还没想到是二分,这道题给了我关于二分用法的一个突破,一开始用了个O(n^2)的暴力算法,但显然是不行的,对于这样的题,想到二分是因为我们可以假设已经找到了这样的最后答案ans,使得对于ans,刚好有M块石头去掉后的最小间距为这些最小间距的最大值为ans。

下面先附上二分算法的代码,然后基于其给予讲解:

        while(left<=right)
        {
            mid=(left+right)/2;
            int start=0;
            int sum=0;
            for(int i=1; i<=N; i++)
            {
                if(a[i]-start>=mid)
                {
                    start=a[i];
                }
                else sum++;
            }
            if(sum<=M)
            {
                left=mid+1;
                ans=mid;
            }
            else
            {
                right=mid-1;
            }
        }

我们的利用left与right分别为左端口与右端口,mid为其平均值,在for语句内,我们一一对每个石头进行操作(这样的时间复杂度为O(n*log n)),假如间距小于mid,那么这块石头就可以移走,所以可移动的石头数+1,反之,间距“>=”mid的时候,意味目前选取的起跳点到目标点的距离超过了mid,不可挪动,将起跳点移位到目标点的位置,再往后判断。接下来我们判断sum的值与left与right之间的关联,如果可移动的石头的数量“>=”M说明有扩充的空间,但为了避免超出,我们用ans保存当前mid下的值(因为其中存在“==”符号,可能有符合的情况),且让left往下走,另一方面,当“>M”时,说明过头了,将右端口往下移。

                if(a[i]-start>=mid)
                {
                    start=a[i];
                }
                else sum++;

    构成二分的基础:讲一下这个if语句的作用,这是这个问题的重难点,对于每一块石头,我们从0作为起跳点,往后,如果这两块石头间的距离大于等于mid,那么说明这块石头得保留下来,作为下一个起跳点,否则的话由于两两距离小于mid,如果不删去的话就与给出的最小值为mid不符合,所以,我们删除这块石头,并往后判断。

    然后这也就反应出我们为什么在"sum“<=”M"而不是“<”的时候进行left=mid+1;与ans=mid,因为这个时候存在:“=”的理由:此时最小距离的值小于ans此时的值。

    可能讲解的不到位,附上AC代码:

#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
typedef long long ll;
int a[50005];
int main()
{
    int L,M,N;
    while(scanf("%d %d %d",&L, &N, &M)!=EOF)
    {
        a[0]=0;
        for(int i=1; i<=N; i++)
        {
            scanf("%d",&a[i]);
        }
        sort(a+1, a+N+1);
        N++;
        a[N]=L;
        int left,right,mid;
        int ans=0;
        left=0;
        right=L;
        while(left<=right)
        {
            mid=(left+right)/2;
            int start=0;
            int sum=0;
            for(int i=1; i<=N; i++)
            {
                if(a[i]-start>=mid)
                {
                    start=a[i];
                }
                else sum++;
            }
            if(sum<=M)
            {
                left=mid+1;
                ans=mid;
            }
            else
            {
                right=mid-1;
            }
        }
        printf("%d\n",ans);
    }
    return 0;
}


猜你喜欢

转载自blog.csdn.net/qq_41730082/article/details/80657348
今日推荐