2.过河问题(river.c/cpp)
[问题描述]
有一个大晴天,Oliver与同学们一共N人出游,他们走到一条河的东岸边,想要过河到西岸。而东岸边有一条小船。
船太小了,一次只能乘坐两人。每个人都有一个渡河时间T,船划到对岸的时间等于船上渡河时间较长的人所用时间。
现在已知N个人的渡河时间T,Oliver想要你告诉他,他们最少要花费多少时间,才能使所有人都过河。
注意,只有船在东岸(西岸)的人才能坐上船划到对岸。
[输入格式]
输入文件第一行为人数N,以下有N行,每行一个数。
第i+1行的数为第i个人的渡河时间。
[输出格式]
输出文件仅包含一个数,表示所有人都渡过河的最少渡河时间。
[样例输入]
4
6
7
10
15
[样例输出]
42
[样例解释]
初始:东岸{1,2,3,4},西岸{}
第一次:东岸{3,4},西岸{1,2} 时间7
第二次:东岸{1,3,4},西岸{2} 时间6
第三次:东岸{1},西岸{2,3,4},时间15
第四次:东岸{1,2},西岸{3,4} 时间7
第五次:东岸{},西岸{1,2,3,4} 时间7
所以总时间为7+6+15+7+7=42,没有比这个更优的方案。
[数据范围]
对于40%的数据满足N≤8。
对于100%的数据满足N≤100000。
贪心动规,最近的贪心动规做得有点多,有点难度。
前面是很多个特判。这个方法是汪维正讲的。
枚举送慢两个人过河。首先观察可以发现,过河时间取决于较慢的人,所以每次过河都尽量让快的来传递船。
首先1和n过河,代价为c[n],然后1回来送船,代价为c[1],然后和n-1过河,代价为c[n-1],然后回来送船,代价为c[1]。接下来便是相同子问题。
总的为2*c[1]+c[n]+c[n-1]。
这个贪心不完整。有可能两个较慢的一起过河,然后同时两个最慢的都到达对岸了。为了不让他们回来,所以要先让一个速度较快的到达对岸,则1和2一起过河,然后1回来送船,然后较慢的两个一起过河,然后2把船送回来。即f[2]+f[1]+f[n]+f[2]
排序后,
f[l] = f[l-2]+(t[l]+t[1]+t[l-1]+t[1]<?t[l]+t[1]+t[2]+t[2]);
边界为f[2]==t[2]。f[3]=t[1]+t[2]+t[3]
//#include <iostream>
//using std::cout;
//using std::cin;
#include <cstdlib>
#include <cstdio>
const long oo = 0x7fff0000;
long n;
long t[100002];
long f[100002];
int bigger(const void* a,const void* b)
{
long aa = *(long*)a;
long bb = *(long*)b;
if (aa>bb)return 1;
if (aa<bb)return -1;
return 0;
}
void dp(long l)
{
if (f[l]>0)
return;
if (l>=4)
{
dp(l-2);
f[l] = f[l-2]+(t[l]+t[1]+t[l-1]+t[1]<?t[l]+t[1]+t[2]+t[2]);
}
}
int main()
{
freopen("river.in","r",stdin);
freopen("river.out","w",stdout);
scanf("%ld",&n);
for (long i=1;i<n+1;i++)
scanf("%ld",t+i);
qsort(t+1,n,sizeof(long),&bigger);
if (n==0)
{
printf("0");
return 0;
}
if (n==1)
{
printf("%ld",t[1]);
return 0;
}
if (n==2)
{
printf("%ld",t[2]);
return 0;
}
if (n==3)
{
printf("%ld",t[1]+t[2]+t[3]);
return 0;
}
f[2] = t[2];
f[3] = t[1]+t[2]+t[3];
dp(n);
printf("%ld",f[n]);
return 0;
}