题解:洛谷 B3928 田忌赛马

【题目来源】

洛谷:B3928 [GESP202312 四级] 田忌赛马 - 洛谷

【题目描述】

你要和田忌赛马。你们各自有 \(N\) 匹马,并且要进行 \(N\) 轮比赛,每轮比赛,你们都要各派出一匹马决出胜负。

你的马匹的速度分别为 \(u_1,u_2,\cdots,u_n\),田忌的马匹的速度分别为 \(v_1,v_2,\cdots,v_n\)。田忌会按顺序派出他的马匹,请问你要如何排兵布阵,才能赢得最多轮次的比赛?巧合的是,你和田忌的所有马匹的速度两两不同,因此不可能出现平局。

【输入】

第一行一个整数 \(N\)。保证 \(1\le N \le 5\times 10^4\)

接下来一行 \(N\) 个用空格隔开的整数,依次为 \(u_1,u_2,\cdots,u_n\),表示你的马匹们的速度。保证 \(1\le u_i\le 2N\)

接下来一行 \(N\) 个用空格隔开的整数,依次为 \(v_1,v_2,\cdots,v_n\),表示田忌的马匹们的速度。保证 \(1\le v_i\le 2N\)

【输出】

输出一行,表示你最多能获胜几轮。

【输入样例】

3
1 3 5
2 4 6

【输出样例】

2

【核心思想】

  1. 问题分析:给定双方各 \(N\) 匹马的速度,田忌按固定顺序派出马匹,我方可以自由安排出场顺序。求最多能获胜的轮次。这是一个贪心匹配问题,核心在于对双方马匹排序后,用双指针策略找到最优匹配方案。

  2. 算法选择

    • 排序 + 双指针:将双方马匹速度分别排序,用贪心策略为田忌的每匹马匹配能击败它的最慢的马
  3. 关键步骤

    • 读入数据:读取 \(N\),数组 \(u[1..N]\)(我方),\(v[1..N]\)(田忌)
    • 排序\(sort(u+1, u+N+1)\)\(sort(v+1, v+N+1)\)
    • 双指针匹配
      • \(j \leftarrow 1\)(我方指针)
      • 遍历田忌的马 \(i\)\(1\)\(N\)
        • 跳过所有 \(u[j] < v[i]\) 的马(无法获胜)
        • \(j \leq N\)\(ans \leftarrow ans + 1\)\(j \leftarrow j + 1\)(用当前马击败田忌的马)
        • 否则 \(break\)(无可用马匹)
    • 输出结果\(ans\)
  4. 时间/空间复杂度

    • 时间复杂度:\(O(N \log N)\),排序主导
    • 空间复杂度:\(O(N)\),存储数组
  5. 贪心匹配与双指针的核心思想

    • 排序转化问题:田忌的马按出场顺序固定,但我方可以重新排列。将双方排序后,问题转化为:在有序序列中,我方每个元素最多使用一次,求能大于对方对应元素的最大匹配数
    • 最慢获胜策略:对于田忌的每匹马 \(v[i]\),选择能击败它的最慢的马 \(u[j]\)。这样保留更快的马去应对更强的对手,是全局最优的贪心策略
    • 双指针的单调性:双方排序后,若 \(u[j]\) 无法击败 \(v[i]\),则 \(u[1..j]\) 都无法击败 \(v[i]\)\(j\) 只需单向右移,无需回溯
    • 田忌赛马的变体:经典田忌赛马是双方同时自由安排顺序,本题是田忌固定顺序、我方自由安排,简化为贪心匹配问题
    • 适用于有序序列的贪心匹配、资源最优分配类问题

【算法标签】

普及- #双指针

【代码详解】

#include <bits/stdc++.h>
using namespace std;

const int N = 50005;  // 最大数组长度
int n;                // 数组大小
int ans;              // 答案:满足条件的配对数量
int u[N], v[N];       // 两个数组

int main()
{
    // 输入数组大小
    cin >> n;

    // 输入并排序数组u
    for (int i = 1; i <= n; i++)
    {
        cin >> u[i];
    }
    sort(u + 1, u + n + 1);

    // 输入并排序数组v
    for (int i = 1; i <= n; i++)
    {
        cin >> v[i];
    }
    sort(v + 1, v + n + 1);

    // 双指针贪心匹配
    int j = 1;  // u数组的指针

    // 遍历v数组
    for (int i = 1; i <= n; i++)
    {
        // 跳过所有小于v[i]的u[j]
        while (j <= n && u[j] < v[i])
        {
            j++;
        }

        // 调试输出
        // cout << "j " << j << endl;

        // 如果找到满足条件的u[j](即u[j] >= v[i])
        if (j <= n)
        {
            ans++;  // 成功配对数加1
            j++;    // 这个u[j]已被使用,指针后移
        }
        else
        {
            // 没有更多可用的u,提前结束
            break;
        }
    }

    // 输出结果
    cout << ans << endl;

    return 0;
}

【运行结果】

3
1 3 5
2 4 6
2
posted @ 2026-07-17 16:10  团爸讲算法  阅读(8)  评论(0)    收藏  举报