【每日算法】贪心法过河

题目大意:

有n个人要过一条河,每个人过河都需要一个时间aiai,有一艘船,每次过河只能最多装两个人。两个人划船过河所需的时间都取决于过河时间长的那个人。比如,A,B两人过河所需时间分别为a,b,那么,他们成一条船过河所需的时间为:max{a,b}。现在让你安排一个过河方案,让所有人用最短的时间全部过河。

问题分析:

首先,我们先来研究一个问题,就是在没过河的人有4个及4个以上的情况下,我们设其中四人为a、b、c、d,并且所需时间a<b<c<d,那么,我现在想让c、d过河,然后再让船回到过河前的位置,准备好继续送其他的人过河。那么我这里有两种运载方式:

1.过河顺序为:ac、a、ad、a 时间消耗:t1=2a+c+d

2.过河顺序为:ab、a(b)、cd、b(a) 时间消耗:t2=a+2b+d

t1−t2=a+c−2b

这也就是说,选择两种方案的哪一种,和a+c−2b的值有关。

对于第一种的解释:我就是让,所需时间最小的a来分别送c、d过河,因为a所需时间最少,所以每次a把船送回来所需的时间也是最少的。所以选择a来送有可能是最优方法。对于第二种的解释:如果c、d过河都需要很长的时间,那么,我就让他们一起过去,这样就可以很有效的去除掉相比较来说所需时间较小的c的过河时间,然后我再让一个提前在对岸守好的b(之前由a送到对岸),再来把船还回去。所以这是唯一两种比较有前途的送c、d过河的方式。

那么现在我设定n个人过河“xcx的贪心策略”为:在要过河人数n≥4的时候,我先用上述两种方法中较好的一个,把最大的两个送过河(用过河时间最少的人作为上述方法的a,第二少的作为上述方法的b)。然后该问题就变成了:寻找把剩下的n-2个人送过河的最优策略。反复执行此策略,直到n=2时,显然两个人直接过去就行了,n=3时,用最小的分别把两个送过去为最优(三个人过河,显然就是:过去两个人,回来一个,在过去两个,两次过去两个的花费分别为:b、c,那这个回来的人,应该是a才能最快,也就是让a分别送b、c。)

 

 

下面我们来证明为什么该策略为最优策略:

一.首先证明,先送的应该是最慢的两个:

1.如果a1+an−1−2a2<0,那么,就说明,用最小a1a1的分别单独送an−1,an优于让an−1和an一起过河。那么这样,由于其他的ai(i<n−1)<an−1,所以,a1+ai(i<n−1)−2a2<00,所以让最小的a1分别送所有的人,优于让他们中的任意两个人组合一起过河。所以这种情况下过河的总过程可以看成是,判断最慢的两个人是否应该一起过河,然后再继续判断下一个。

2.如果存在若干个aiai满足a1+ai−2a2>0,那么也就是说明,满足这个条件的这些aiai都有一个特点,那就是,让他和任意一个比他大的一起过河,都应该是要优于让最小的分别送他和那个比他大的过河。那现在,我想要说明的是,对于这些满足这个条件的aiai,应该让他们怎么组合,才能得到最优的结果呢:

设:x=2a2−a1。

那么,a1≤a2≤....≤ai−1≤x≤ai≤...≤an

现在,这个x就是一个分界点,x左边的,让a1分别送最好,x右边的,任意两个组合都比这两个单独让a1送要快。我假设,让ai和an一起,aj和an−1一起,那么他们的时间花费为:an−1+an。而如果我让an−1和an一起,ai和ajai和aj一起,那么现在该策略的花费为:an+an+max{ai,aj}≤an−1+an。由此我们的出结论,让an−1和an在一起一定优于让他们分别和其他的任意一个组合一起过河。

 

二.然后下面我们来证明,为什么如果要用最快的两个人帮助运送其他的人:

如果单独过,显然让最快的a1来送,这个就不用说了。然后要说明的是:如果是要送的两个人要一起坐船更优,那么也显然是借用最快的a1,a22来进行船的调度最快。用类似的方法设用ai,aj进行船的调度,然后可以证明该选法不会比选a1,a2更好(只会更惨)。同时,选用a1,a2也可以使得2a2−a1也就是x的值尽可能的低,这样也就能让更多的ai可以选择与其他更慢的一起走了ai可以选择与其他更慢的一起走了

 

 

综合上面所述,我们证明了“xcx的贪心策略”的正确性,同时,我们还发现,可以对该策略进行改新。下面推出改进后的“xcx的贪心策略2.0”:

每次判断最慢的两个一起运是不是要比分别由最快的送要快,如果是,让这两个一起过去,然后在继续判断,直到某一次,发现,用最快的分别运要更快,然后之后所有的,都让最快的分别一个一个的运。

 

 

下面是代码。

#include<iostream>
#include<math.h>
#include<string.h>
#include<algorithm>
using namespace std;

int test;
int n;
int a[1100]={0};
int main()
{
    cin>>test;
    while(test--)
    {
        cin>>n;
        for(int i=0;i<n;i++)
        {
            scanf("%d",&a[i]);
        }
        sort(a,a+n);
        long long int num=0;
        if(n==1)
        {
            cout<<a[0]<<endl;continue;
        }
        if(n==2)
        {
            cout<<a[1]<<endl;continue;
        }
        if(n==3)
        {
            for(int i=n-1;i>0;i--)
            {
                num=num+a[i]+a[0];
            }
            num=num-a[0];
            cout<<num<<endl;
            continue;
        }
        int x=2*a[1]-a[0];
        int i;
        for(i=n-2;i>=2;i=i-2)
        {
            if(a[i]<x)break;
            num=num+a[i+1]+a[0]+2*a[1];
        }
        i++;
        for(;i>=1;i--)
        {
            num=num+a[i]+a[0];
        }
        num=num-a[0];
        cout<<num<<endl;    
    }
}

后记:

这个其实社团培训的时候被当作例题讲过,但是当时我并没有理解。在这里有一个建议,在用贪心算法的时候,一定一定一定不要想当然,尽量从代数的角度或者逻辑的角度证明一下,不然很有可能是错误的。就算开发一下思维也是好的。描述贪心策略的时候也一定要明确表明,你的贪心策略的局部最优解是什么,(策略2.0的局部最优解就是把“最慢”的两个送过去所需的最少时间,)该局部最优解是否可以得到全局最优解。也就是要明确你的贪心方法做的每一步都是正确的。在证明贪心策略的正确性的时候,有一个很套路的说法,就是,想办法证明不会有什么选法比你的贪心策略的选法更优(在某种特定情况,某些特定选法可能和你的贪心策略一样优)。

猜你喜欢

转载自blog.csdn.net/weixin_41484240/article/details/82025994
今日推荐