Luogu P1650/POJ2287 两序列比较的优胜次数最大化的贪心博弈问题(田忌赛马) 以及基于元素的贪心法证明

综述

贪心法容易理解,但不容易构想出来。
关于理解完全可以使用反证的思路,假设某一步不这么走,就不能取得最优。这句话正是贪心法证明的核心。我们将通过法一对这个证明方法加以具体化说明,尽管法一是基于元素的贪心。
另外多种贪心方法最后都可以证明是同质的。这将在法二中给予说明。

问题源址

描述

田忌赛马的故事大家都很熟悉了。现在不止给出3匹马,田忌赢一场给200两银子,输一场赔200两,平局不赔。

输入

第一行:赛马的匹数。
第二行:田忌的马的质量
第三行:齐王的马的质量。

输出

田忌最多能赢多少钱。

示例

//input 1
7
97 94 94 82 60 57 44 
94 94 94 82 71 64 60

//output 1
400

法一

田忌赛马问题的核心就是对两端的即将退化的元素进行操作。序列两端的马往往可以找到对应确定的对手。这是一种基于元素的贪心法。另外有基于步数的贪心。

这个确定的最优关系是贪心法证明的实质。通过对序列中每一个元素都找到对应的最“佳”对手——换句话说是对田忌更有利的对手——最终的结果将是田忌利益最大化。

所以我们对两端进行如下三个重要选择:

  1. 如果我的最好的马好于对方最好:那么如果不用这匹马赢掉对方最好的,将会使得这次比赛之后,虽然也能赢,但对方的马群整体质量上升(一匹相对差的马换掉一匹更好的马),就不能取得最优
  2. 如果我的最坏的马好于对方最坏:那么如果不用这匹马赢掉对方最坏的,将会使得这次比赛之后,虽然也能赢这一句,但我方的马群整体质量下降(一匹更好的马替代本来最坏的去赢比赛),也不是最优情况
  3. 如果这两个条件都不满足:采用最坏换最好。
    1. 若两端比较关系中存在一个‘坏于’关系。那么很确定地要进行以最坏换最好。(都是在根本不可能赢的情形下减小失败成本,使得我方水平相对上升)。如果不这么比,那么我们用了一个更好的马代替最坏的马去死,那么我方马群质量下降,不是最优情况
    2. 若两端都是相同,那么由于两个序列本身具有单调性,所以在最坏换最好的时候,进行三次比赛必定产生两胜一负。如果不采用这种方法,而分别用两端好对好、坏对坏进行比赛,那么最好的情况是两次平手+一次胜,由于还存在最后一次不能取胜的情况,所以不是最优情况。使用最坏换最好是可行的。这里结合了步数贪心的想法。

关于最坏换最好的一个说明:如果田忌的最坏不小于齐王的最好,那么

t[tw] >= q[qb];//例外情况
t[tw] <= q[qw];//潜在条件
q[qb] >= q[qw];

得到全部相等。可以break出去了,没必要加特判

#include 
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. 如果最好的马好于对方,同法一的1,最好比最好
  2. 如果最好的马坏于对方,也确定。同法一3.1,最坏换最好
  3. 如果最好的马相同,一般不能用来这么浪费。通过法一中的分析,硬刚往往不如最小换最大带来的增益好。
    1. 如果最坏的马好于对方,同法一2,最坏比最坏。
    2. 如果最坏的马等于或坏于对方,同法一3.2,最坏换最好。其中要对田忌的最坏好于齐王的最好马给予特判。

严格来说齐王的马每一匹应该都好于田忌,不需要这种特判,但是实际上从测试数据看,这个情况可能存在。

另外关于3,是一个很玄学的讲法。这或许告诉我们做人呐,硬碰硬会造成内耗。看似用没那么好的一面对外的时候,使得自己整体得到了优化。这岂不是二年级教室墙上挂的“谦虚使人进步” hhhh……

分析这五个分支,所干的只有三件事,也就是用最坏比最坏,最好比最好,以及最坏换最好。那么也就等价于法一当中的三个分支了。与法一的不同在于其先盯着一端看。这个方法或许更加接近于问题解决的思路,但是作为程序来说确实繁琐了点。

#include 
#include 
#include 
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: 我感觉不是我的大脑,而是我的笔在思考

你可能感兴趣的:(C/C++,#,贪心法,题解)