POJ - 1700 Crossing River解题报告(过河问题的贪心策略)

题目大意:

有n个人要过一条河,每个人过河都需要一个时间 ai ,有一艘船,每次过河只能最多装两个人。两个人划船过河所需的时间都取决于过河时间长的那个人。比如,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

t1t2=a+c2b

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

对于第一种的解释:我就是让,所需时间最小的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+an12a2<0 ,那么,就说明,用最小 a1 的分别单独送 an1,an 优于让 an1an 一起过河。那么这样,由于其他的 ai(i<n1)<an1 ,所以, a1+ai(i<n1)2a2<0 ,所以让最小的 a1 分别送所有的人,优于让他们中的任意两个人组合一起过河。所以这种情况下过河的总过程可以看成是,判断最慢的两个人是否应该一起过河,然后再继续判断下一个。

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

设: x=2a2a1

那么, a1a2....ai1xai...an

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

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

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

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

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

下面是代码。

#include
#include
#include
#include
using namespace std;

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

后记:

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

你可能感兴趣的:(贪心)