【题解】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\n",f[n]);
	return 0;
}
posted @ 2020-05-11 21:45  _SingerCoder  阅读(290)  评论(0编辑  收藏  举报