【区间dp】-----例题5【田忌赛马】(暂时只会贪心解法)

P1650 田忌赛马

题目链接

题目描述

我国历史上有个著名的故事: 那是在 2300 2300 2300 年以前。齐国的大将军田忌喜欢赛马。他经常和齐王赛马。他和齐王都有三匹马:常规马,上级马,超级马。一共赛三局,每局的胜者可以从负者这里取得 200 200 200 银币。每匹马只能用一次。齐王的马好,同等级的马,齐王的总是比田忌的要好一点。于是每次和齐王赛马,田忌总会输 600 600 600 银币。

田忌很沮丧,直到他遇到了著名的军师――孙膑。田忌采用了孙膑的计策之后,三场比赛下来,轻松而优雅地赢了齐王 200 200 200 银币。这实在是个很简单的计策。由于齐王总是先出最好的马,再出次好的,所以田忌用常规马对齐王的超级马,用自己的超级马对齐王的上级马,用自己的上级马对齐王的常规马,以两胜一负的战绩赢得 200 200 200 银币。实在很简单。

如果不止三匹马怎么办?这个问题很显然可以转化成一个二分图最佳匹配的问题。把田忌的马放左边,把齐王的马放右边。田忌的马 A 和齐王的 B 之间,如果田忌的马胜,则连一条权为 200 200 200 的边;如果平局,则连一条权为 0 0 0 的边;如果输,则连一条权为 − 200 -200 200 的边……如果你不会求最佳匹配,用最小费用最大流也可以啊。 然而,赛马问题是一种特殊的二分图最佳匹配的问题,上面的算法过于先进了,简直是杀鸡用牛刀。现在,就请你设计一个简单的算法解决这个问题。

输入格式

第一行一个整数 n n n ,表示他们各有几匹马(两人拥有的马的数目相同)。第二行 n n n 个整数,每个整数都代表田忌的某匹马的速度值($0 \le $ 速度值 ≤ 100 \le 100 100)。第三行 n n n 个整数,描述齐王的马的速度值。两马相遇,根据速度值的大小就可以知道哪匹马会胜出。如果速度值相同,则和局,谁也不拿钱。

输出格式

仅一行,一个整数,表示田忌最大能得到多少银币。

输入输出样例 #1

输入 #1

3
92 83 71
95 87 74

输出 #1

200

说明/提示

数据规模与约定

  • 对于 20 % 20\% 20% 的数据, 1 ≤ N ≤ 65 1\le N\le 65 1N65
  • 对于 40 % 40\% 40% 的数据, 1 ≤ N ≤ 250 1\le N\le 250 1N250
  • 对于 100 % 100\% 100% 的数据, 1 ≤ N ≤ 2000 1\le N\le2000 1N2000

题解:(贪心)

一、贪心策略详解

采用如下贪心策略来最大化田忌的得分:

  1. 田忌和齐王的马按速度从高到低排序
  2. 使用双指针:
    • tfirsttendd 分别指向田忌最快和最慢的马;
    • qfirstqendd 分别指向齐王最快和最慢的马。
  3. 每次比较当前最快的两匹马,根据情况做出决策:

决策逻辑如下:

情况策略得分变化
田忌快马 > 齐王快马直接比一场,赢了+200
田忌快马 < 齐王快马用田忌最慢的马去输掉齐王最快的马(牺牲)-200
田忌快马 == 齐王快马查看最慢马是否能赢对方最慢马:
- 能赢 → 最慢马 vs 最慢马,赢
- 否则 → 牺牲最慢马
+200 或 -200

二、贪心策略的正确性证明

1. 当田忌快马 > 齐王快马时:直接比赛

这是显然正确的。因为如果不打这一场,而是用这匹好马去对齐王的一匹慢马,虽然也能赢,但会浪费这匹快马的优势,可能影响后续更关键的比赛。

结论:此时应该直接比,赢得 200 分。


2. 当田忌快马 < 齐王快马时:用最慢马去牺牲

田忌的快马不能赢,那么它最多只能和对方慢马打成平局或输给对方慢马。但如果我们用最慢马去对齐王快马,就能保留住田忌的快马用于后续比赛。

结论:牺牲最慢马是明智的选择。


3.当田忌快马 == 齐王快马时:(核心)

这时候我们需要考虑如何安排当前马匹以获得最大收益。

(a) 如果田忌最慢马 > 齐王最慢马:

那就让这两匹马比赛,赢得 200 分,保留快马应对后面的对手。

(b) 如果田忌最慢马 <= 齐王最慢马

那无论如何都赢不了,不如用这匹慢马去对齐王快马,避免浪费其他好马。

结论:在这种情况下,优先考虑用最慢马赢对方最慢马;否则就牺牲最慢马。

  • 注意这里 牺牲最慢马的 代码逻辑(不要对 平局 实施 报复性扣分

综合以上三点,贪心策略是局部最优且全局最优的。


三、完整代码

#include <iostream>
#include <vector>
#include <algorithm>
using ll = long long;
using namespace std;

int main() {
    ll n;
    cin >> n;  // 输入马的数量

    // 定义两个数组存储田忌和齐王的马的速度
    vector<ll> t(n);  // 田忌的马
    vector<ll> q(n);  // 齐王的马

    // 输入田忌的马的速度
    for (ll i = 0; i < n; i++) cin >> t[i];

    // 输入齐王的马的速度
    for (ll i = 0; i < n; i++) cin >> q[i];

    // 将田忌和齐王的马按速度从大到小排序(降序)
    sort(t.begin(), t.end(), greater<ll>());
    sort(q.begin(), q.end(), greater<ll>());

    ll ender = 0;  // 记录最终得分,初始为0

    // 双指针策略:
    ll tfirst = 0, tendd = n - 1;  // 田忌最快和最慢的马的下标
    ll qfirst = 0, qendd = n - 1;  // 齐王最快和最慢的马的下标

    // 开始比赛:只要还有马没有比完,就继续循环
    while (tfirst <= tendd) 
    {
        // 情况一:田忌当前最快的马 > 齐王当前最快的马
        if (t[tfirst] > q[qfirst]) 
        {
            ender += 200;  // 赢得本场比赛,加200分
            tfirst++;      // 田忌向右移动一位(下一匹最快的马)
            qfirst++;      // 齐王也向右移动一位
        }
        // 情况二:田忌当前最快的马 < 齐王当前最快的马
        else if (t[tfirst] < q[qfirst]) 
        {
            ender -= 200;  // 输掉本场比赛,扣200分
            tendd--;       // 田忌牺牲最慢的马(向左移动)
            qfirst++;      // 齐王使用当前最快的马去赢,所以向右移动
        }
        // 情况三:田忌当前最快的马 == 齐王当前最快的马(平局)【核心点】
        else 
        {
            // 先尝试用田忌最慢的马 vs 齐王最慢的马
            if (t[tendd] > q[qendd]) 
            {
                ender += 200;  // 如果能赢,则让这两匹慢马比赛
                tendd--;       // 田忌和齐王都去掉最慢的马
                qendd--;
            }
            // 如果田忌最慢马无法战胜齐王最慢马
            else 
            {
                // 此时无论是否平局,都选择用田忌最慢马去对齐王最快的马(牺牲)
                
                // 走到这个分支,当前田忌最慢马一定无法战胜齐王最快马:
				// - 如果田忌最慢马 < 齐王最快马 → 输掉比赛,扣200分
                if (t[tendd] < q[qfirst]) 
                {
                    ender -= 200;  // 如果输的话扣200分
                }
	             
	            // - 如果等于,则为平局,不加分也不扣分
                tendd--;   // 田忌最慢马已用,去掉
                qfirst++;  // 齐王当前最快马已用,去掉
            }
        }
    }

    // 输出最终得分
    cout << ender << endl;

    return 0;
}

四、常见实现误区与注意事项

在实现田忌赛马问题的过程中,一个常见的逻辑错误出现在处理

快马 == 快马 且 慢马 <= 慢马

的情况时

1.错误写法示例:

else {
    ender -= 200;  // 直接扣分
    tendd--;
    qfirst++;
}

这种写法的问题在于:无论田忌最慢马是否能和齐王最快马打平 或 打赢,都直接扣200分。这会导致以下一种合法情况被错误地当作失败处理:

情况实际结果是否应该扣分
田忌慢马 == 齐王快马平局❌ 不应扣分

为了更好地说明这个问题,我们来看两组测试样例。


🔍 样例1:田忌慢马 < 齐王快马(应扣分)

输入:

2
3 1
4 2

排序后:

  • 田忌:[3, 1]
  • 齐王:[4, 2]

执行过程:

  • 第一轮:3 < 4 → 牺牲慢马 1 vs 4 → 扣200分
  • 第二轮:3 vs 2 → 赢 +200分

✅ 正确得分:0
❌ 若不加判断强制扣分,最终得分为 -200(错误)


🔍 样例2:田忌慢马 == 齐王快马(不应扣分)

输入:

2
3 2
3 1

排序后:

  • 田忌:[3, 2]
  • 齐王:[3, 1]

执行过程:

  • 第一轮:快马相等,慢马 2 > 1 → +200分
  • 第二轮:快马相等,慢马 == 快马 → 不加分也不扣分

✅ 正确得分:200
❌ 若不加判断强制扣分,最终得分为 0(错误)


2.正确做法:

只有当田忌慢马 确实小于 齐王快马时才应当扣分:

else 
{
    if (t[tendd] < q[qfirst])
        ender -= 200;  // 只有输的时候才扣分
    tendd--;
    qfirst++;
}

这样就能保证得分系统准确反映比赛结果,避免对平局做出惩罚性扣分


四、时间复杂度分析

操作时间复杂度
排序O(n log n)
主循环O(n)
总体O(n log n)
### 贪心算法解决田忌赛马问题 #### 问题描述 齐威王和田忌各有三匹不同等级的马,分别为上、中、下三个级别。每场比赛双方各派出一匹马进行比赛,胜利者获得一分。由于齐威王每一级别的马都比田忌相应级别的马要强,如果按照同级对阵的方式,则田忌必定失败。 为了使田忌能够赢得更多场次的比赛,可以通过调整出战顺序来优化结果。具体来说,就是通过合理的安排让田忌尽可能多地赢取分数[^1]。 #### 实现逻辑 贪心算法的核心在于每次决策时做出当前看来最好的选择,即局部最优解。对于田忌赛马而言: - 将两个选手所有的马按速度降序排列; - 对于田忌最慢的一匹马,在齐威王所有未使用的马里找到最快但仍然能战胜这匹马的那一匹作为对手;如果没有这样的马,则放弃这场比赛; - 如果还有剩余的比赛机会,则重复上述过程直到完成全部匹配或无法再增加得分为止。 这种方法确保了每一次都能最大化利用现有资源去争取更多的胜场数,从而达到全局最优的效果[^2]。 #### Python代码示例 下面是一个简单的Python程序实现了这个想法: ```python def tianji_race(tianji, qiwang): # 排序两者的马的速度列表 tianji.sort(reverse=True) qiwang.sort() tj_score = 0 while tianji and qiwang: if tianji[-1] > qiwang[0]: tj_score += 1 del tianji[-1], qiwang[0] elif tianji[-1] < qiwang[0]: del tianji[-1] else: break return tj_score if __name__ == "__main__": # 测试数据 tianji_horses_speeds = [9, 7, 5] qi_wang_horses_speeds = [8, 6, 4] score = tianji_race(tianji_horses_speeds[:], qi_wang_horses_speeds[:]) print(f"Tian Ji wins {score} races.") ``` 此段代码模拟了田忌如何根据自己的马与对方马之间的相对速度快慢来进行最佳组合搭配以获取最高积分的过程[^3]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值