题目分析
-
现有n个人在东岸,要过河去西岸。开始东岸有一艘船,船最多可承载2人,过河时间以耗时最长的人所需时间为准。
给定n个人的过河时间a,求所有人从东岸到西岸所需的最短时间。
-
当(n=2)时,易得答案为(a[2])。但问题在于当(n>2)如何解决。
贪心分析
-
对所有数据从小到大排序。
-
从小规模数据出发,当(n=3)时,不难想到可以先用(a[1])将(a[3])送过去,再让(a[1])回来,这样就回归了(n=2)的情况。
-
(n>3)时情况略为复杂,我们考虑推导问题的几个简单性质。
-
对于每次船在西岸时,如果需要某个人将船送回来,则优先让西岸a最小的(dalao)回来。
证明:假设西岸有过河时间(a[1],a[2],a[3])的三个人,则让(a[1])回来,必然导致东岸后续的子问题最优。
如此便得到一个简单的贪心,我们便无需考虑回来的情况,只需要思考如何将人送过去。
重要推论:在问题结束前,不甘寂寞的(dalao) (a[1])不可能被留在西岸。也就是说,(a[1])只要过去,就必然要将船送回来。
-
对于(4<=i<=n)的所有(a[i-1]),它到西岸只有两种可能方式:自己和(a[1])过去,或者自己和(a[i])过去。
对于第一种方式:就相当于(a[i-1])自己过去,(a[1])负责送船回来,代价(a[i-1]+a[1])。
对于第二种方式:如果不希望(a[i-1])被贡献到答案中,则只能选择一个(j>i-1)的小蒟蒻(a[j])来当(a[i-1])的拖油瓶。
但问题在于,过弱的蒟蒻(a[j])不能和(dalao) (a[i-1])玩,或者说(dalao) (a[i-1])不能选择太弱的(a[j])来当拖油瓶,否则代价会过大。因此(a[i-1])只能选择(a[i])来当它的拖油瓶。
那么两人过去以后,谁给他们送船回来呢?由于(a[1])不可能在西岸等着他们,因此需要提前让(a[1])带(a[2])过去,留(a[2])为他们送船。代价(a[2]+a[1]+a[i]+a[2])。
-
由此,我们可以得到状态转移方程:$$f[i]=min(f[i-1]+a[i]+a[1],f[i-2]+a[2]+a[1]+a[i]+a[2])$$
-
几乎每一篇题解都提到了这个方程,那么问题来了,为什么(f[i])由(f[i-1]和f[i-2])中取一转移即可,而不能继续考虑到(f[i-3])?
我们考虑已知(f[i-3]),然后新来了三个蒟蒻,对于(a[i-2]),他不可能会带(a[i])过河,也就是要么自己走,要么带(a[i-1])走。也就是说(a[i-2])的决策与(a[i])无关。
因此求(f[i])时,只要考虑(a[i-1])的决策即可,也就是从(f[i-1])和(f[i-2])两个状态中取一转移即可。
-
code:
#include<cstdio>
#include<algorithm>
using namespace std;
const int MAXN=100010;
int n,a[MAXN],f[MAXN];
int main()
{
// freopen("in.txt","r",stdin);
scanf("%d",&n);
for(int i=1;i<=n;++i)scanf("%d",a+i);
f[1]=a[1];
f[2]=a[2];
f[3]=a[3]+a[1]+a[2];
for(int i=4;i<=n;++i)f[i]=min(f[i-1]+a[i]+a[1],f[i-2]+a[2]+a[1]+a[i]+a[2]);
printf("%d
",f[n]);
return 0;
}