有一类贪心问题具有一定的迷惑性,大多数同学初步思考时会很快得到一种‘看上去很正确’的贪心策略,但是对于大部分贪心问题不能贸然得出结论。我们可以尝试构造一些反例推翻该策略。但是,即使该策略被推翻了,也不一定说明它完全错误,还有可能是在不同条件下贪心策略不同,此时需要分类讨论。因此,在解决贪心问题时,需要我们使用“构造方法、反例验证、打表观察、数学推导”等多种方式结合、多次尝试后,才能最终得到正确的方法。
例如过河问题
有四个人要从 A 点坐一条船过河到 B 点,船一开始在 A 点。该船一次最多可坐两个人。 已知这四个人中每个人独自坐船的过河时间分别为 1,2,4,8,且两个人坐船的过河时间为两人独自过河时间的较大者。则最短( )时间可以让四个人都过河到 B 点(包括从 B 点把船开回 A 点的时间)。
A. 14
B. 15
C. 16
D. 17
【解析】
深入思考发现每次过河船只能承受两个人,且返回还需要一个人折返的时间,因此折返的工作最好交给时间最短的人和次最短的人去做,尽量减少回程时间,使去程贡献最大。由此想到另外一种贪心策略:首先最快和次快的人先过河,最快的人返回,然后最慢与次慢的人再过河,次快的人返回,最后最快和次快的人过河。
那么进入正题,我们来看一下洛古的P1809 过河问题
过河问题
题目描述
有一个大晴天,Oliver 与同学们一共 N N N 人出游,他们走到一条河的东岸边,想要过河到西岸。而东岸边有一条小船。
船太小了,一次只能乘坐两人。每个人都有一个渡河时间 T T T,船划到对岸的时间等于船上渡河时间较长的人所用时间。
现在已知 N N N 个人的渡河时间 T T T,Oliver 想要你告诉他,他们最少要花费多少时间,才能使所有人都过河。
注意,只有船在东岸(西岸)的人才能坐上船划到对岸。
输入格式
输入文件第一行为人数 N N N,以下有 N N N 行,每行一个数。
第 i + 1 i+1 i+1 行的数为第 i i i 个人的渡河时间。
输出格式
输出文件仅包含一个数,表示所有人都渡过河的最少渡河时间。
样例 #1
样例输入 #1
4 6 7 10 15
样例输出 #1
42
提示
数据范围
对于 40 % 40\% 40% 的数据满足 N ≤ 8 N\le8 N≤8。
对于 100 % 100\% 100% 的数据满足 N ≤ 100000 N\le100000 N≤100000。
样例解释
- 初始:东岸 { 1 , 2 , 3 , 4 } \{1,2,3,4\} {1,2,3,4},西岸 { } \{\} {};
- 第一次:东岸 { 3 , 4 } \{3,4\} {3,4},西岸 { 1 , 2 } \{1,2\} {1,2},时间 7 7 7;
- 第二次:东岸 { 1 , 3 , 4 } \{1,3,4\} {1,3,4},西岸 { 2 } \{2\} {2},时间 6 6 6;
- 第三次:东岸 { 1 } \{1\} {1},西岸 { 2 , 3 , 4 } \{2,3,4\} {2,3,4},时间 15 15 15;
- 第四次:东岸 { 1 , 2 } \{1,2\} {1,2},西岸 { 3 , 4 } \{3,4\} {3,4} 时间 7 7 7;
- 第五次:东岸 { } \{\} {},西岸 { 1 , 2 , 3 , 4 } \{1,2,3,4\} {1,2,3,4} 时间 7 7 7。
所以总时间为 7 + 6 + 15 + 7 + 7 = 42 7+6+15+7+7=42 7+6+15+7+7=42,没有比这个更优的方案。
依据上面的分析思想可写出完整代码如下:
#include<bits/stdc++.h>
using namespace std;
int a[100005];
int main(){
int n;cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i];
}
sort(a+1,a+n+1);
int sum=0;
while(n){
if(n==1){
sum+=a[1];
n=0;
}
else if(n==2){
sum+=a[2];
n=0;
}else if(n==3){
sum+=a[1]+a[2]+a[3];
n=0;
}else if(n>=4){
if(2*a[2]>a[1]+a[n-1]){
sum+=(a[n-1]+a[n])+2*a[1];n-=2;
}else {
sum+=a[2]+a[1]+a[n]+a[2];n-=2;
}
}
}
cout<<sum;
return 0;
}