【题解】p1809 过河问题

原题传送门

题目分析

  • 现有n个人在东岸,要过河去西岸。开始东岸有一艘船,船最多可承载2人,过河时间以耗时最长的人所需时间为准。

    给定n个人的过河时间a,求所有人从东岸到西岸所需的最短时间。

  • (n=2)时,易得答案为(a[2])。但问题在于当(n>2)如何解决。

贪心分析

  • 对所有数据从小到大排序。

  • 从小规模数据出发,当(n=3)时,不难想到可以先用(a[1])(a[3])送过去,再让(a[1])回来,这样就回归了(n=2)的情况。

  • (n>3)时情况略为复杂,我们考虑推导问题的几个简单性质。

    1. 对于每次船在西岸时,如果需要某个人将船送回来,则优先让西岸a最小的(dalao)回来。

      证明:假设西岸有过河时间(a[1],a[2],a[3])的三个人,则让(a[1])回来,必然导致东岸后续的子问题最优。

      如此便得到一个简单的贪心,我们便无需考虑回来的情况,只需要思考如何将人送过去。

      重要推论:在问题结束前,不甘寂寞的(dalao) (a[1])不可能被留在西岸。也就是说,(a[1])只要过去,就必然要将船送回来。

    2. 对于(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])

    3. 由此,我们可以得到状态转移方程:$$f[i]=min(f[i-1]+a[i]+a[1],f[i-2]+a[2]+a[1]+a[i]+a[2])$$

    4. 几乎每一篇题解都提到了这个方程,那么问题来了,为什么(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;
}
原文地址:https://www.cnblogs.com/-SingerCoder/p/12872232.html