综述
贪心法容易理解,但不容易构想出来。
关于理解完全可以使用反证的思路,假设某一步不这么走,就不能取得最优。这句话正是贪心法证明的核心。我们将通过法一对这个证明方法加以具体化说明,尽管法一是基于元素的贪心。
另外多种贪心方法最后都可以证明是同质的。这将在法二中给予说明。
问题源址
描述
田忌赛马的故事大家都很熟悉了。现在不止给出3匹马,田忌赢一场给200两银子,输一场赔200两,平局不赔。
输入
第一行:赛马的匹数。
第二行:田忌的马的质量
第三行:齐王的马的质量。
输出
田忌最多能赢多少钱。
示例
//input 1
7
97 94 94 82 60 57 44
94 94 94 82 71 64 60
//output 1
400
法一
田忌赛马问题的核心就是对两端的即将退化的元素进行操作。序列两端的马往往可以找到对应确定的对手。这是一种基于元素的贪心法。另外有基于步数的贪心。
这个确定的最优关系是贪心法证明的实质。通过对序列中每一个元素都找到对应的最“佳”对手——换句话说是对田忌更有利的对手——最终的结果将是田忌利益最大化。
所以我们对两端进行如下三个重要选择:
- 如果我的最好的马好于对方最好:那么如果不用这匹马赢掉对方最好的,将会使得这次比赛之后,虽然也能赢,但对方的马群整体质量上升(一匹相对差的马换掉一匹更好的马),就不能取得最优。
- 如果我的最坏的马好于对方最坏:那么如果不用这匹马赢掉对方最坏的,将会使得这次比赛之后,虽然也能赢这一句,但我方的马群整体质量下降(一匹更好的马替代本来最坏的去赢比赛),也不是最优情况。
- 如果这两个条件都不满足:采用最坏换最好。
- 若两端比较关系中存在一个‘坏于’关系。那么很确定地要进行以最坏换最好。(都是在根本不可能赢的情形下减小失败成本,使得我方水平相对上升)。如果不这么比,那么我们用了一个更好的马代替最坏的马去死,那么我方马群质量下降,不是最优情况。
- 若两端都是相同,那么由于两个序列本身具有单调性,所以在最坏换最好的时候,进行三次比赛必定产生两胜一负。如果不采用这种方法,而分别用两端好对好、坏对坏进行比赛,那么最好的情况是两次平手+一次胜,由于还存在最后一次不能取胜的情况,所以不是最优情况。使用最坏换最好是可行的。这里结合了步数贪心的想法。
关于最坏换最好的一个说明:如果田忌的最坏不小于齐王的最好,那么
t[tw] >= q[qb];//例外情况
t[tw] <= q[qw];//潜在条件
q[qb] >= q[qw];
得到全部相等。可以break出去了,没必要加特判
#include <bits/stdc++.h>
using namespace std;
int qw[10000001], tj[10000001], ht, hw, tt, tw, ans;
int main()
{
int i, j, k, m, n;
cin >> n;
for (i = 1; i <= n; i++)
scanf("%d", &tj[i]);
for (i = 1; i <= n; i++)
scanf("%d", &qw[i]);
sort(tj + 1, tj + 1 + n);
sort(qw + 1, qw + 1 + n);
ht = hw = 1; //左指针
tt = tw = n; //右指针 越右马越好
for (i = 1; i <= n; i++)
{
if (tj[tt] > qw[tw])
{ //若田忌的快马能赢 加200银子,双方右指针都左移
ans += 200;
tt--;
tw--;
}
else if (tj[ht] > qw[hw])
{ //否则比较双方最慢的马 能赢则双方左指针右移
ans += 200;
ht++;
hw++;
}
else if (tj[ht] < qw[tw])
{ //不行的话就用田忌最坏的马浪费掉齐王的好马
ans -= 200;
ht++; //左指针右移
tw--; //右指针左移
}
}
cout << ans;
return 0;
}
法二
我们看到这个方法也使用了贪心的思路,只不过分支的结构不太相同。但是实质都是寻找绝对确定的最佳对手。
这个方法当中,最好的马排在下标小的一端。但这不会影响说明。
- 如果最好的马好于对方,同法一的1,最好比最好
- 如果最好的马坏于对方,也确定。同法一3.1,最坏换最好
- 如果最好的马相同,一般不能用来这么浪费。通过法一中的分析,硬刚往往不如最小换最大带来的增益好。
- 如果最坏的马好于对方,同法一2,最坏比最坏。
- 如果最坏的马等于或坏于对方,同法一3.2,最坏换最好。其中要对田忌的最坏好于齐王的最好马给予特判。
严格来说齐王的马每一匹应该都好于田忌,不需要这种特判,但是实际上从测试数据看,这个情况可能存在。
另外关于3,是一个很玄学的讲法。这或许告诉我们做人呐,硬碰硬会造成内耗。看似用没那么好的一面对外的时候,使得自己整体得到了优化。这岂不是二年级教室墙上挂的“谦虚使人进步” hhhh……
分析这五个分支,所干的只有三件事,也就是用最坏比最坏,最好比最好,以及最坏换最好。那么也就等价于法一当中的三个分支了。与法一的不同在于其先盯着一端看。这个方法或许更加接近于问题解决的思路,但是作为程序来说确实繁琐了点。
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
const int Max = 10000;
int tian[Max], king[Max];
int i, j, n;
bool cmp(int a, int b) { return a > b; }
int main()
{
cin >> n;
for (i = 1; i <= n; i++)
{
cin >> tian[i];
}
for (i = 1; i <= n; i++)
{
cin >> king[i];
}
sort(tian + 1, tian + 1 + n, cmp);
sort(king + 1, king + 1 + n, cmp);//越右越坏
int ans = 0;
int ii, jj;
for (i = 1, j = 1, ii = n, jj = n; i <= ii;)
{
if (tian[i] > king[j])
{
ans += 200;
i++;
j++;
}
else if (tian[i] < king[j])
{
ans -= 200;
j++;
ii--;
}
else
{
if (tian[ii] > king[jj])
{
ans += 200;
ii--;
jj--;
}
else
{
if (tian[ii] < king[j])
ans -= 200;
ii--;
j++;
}
}
}
cout << ans;
return 0;
}
some trivia
本来先开始以为这两个方法是基于元素和基于步数(过程)的贪心思路。结果写着写着发现这在确定的元素上使用元素贪心,在不确定的两端相等的情况下使用了过程贪心。
- 猜想:对于一个确定的问题,或许贪心的解决思路都是一样的?
- 这或许说明了写文的重要性,正如维特根斯坦所说:
Wittgenstein: 我感觉不是我的大脑,而是我的笔在思考