题解:洛谷 P3078 Poker Hands

【题目来源】

洛谷:P3078 [USACO13MAR] Poker Hands S - 洛谷

【题目描述】

Bessie 和她的朋友们正在玩一种独特的扑克牌游戏,这个游戏使用一副有 \(N\) 种不同牌面(\(1 \leq N \leq 100,000\))的牌组,牌面被方便地编号为 \(1\)\(N\)(普通的牌组有 \(N = 13\))。在这个游戏中,牛们只能打出一种牌型:可以选择一张标号为 \(i\) 的牌和一张标号为 \(j\) 的牌,并打出从 \(i\)\(j\) 的所有牌。这种牌型称为「顺子」。

Bessie 的手牌中当前持有 \(a_i\) 张牌面为 \(i\) 的牌(\(0 \leq a_i \leq 100000\))。帮助她找到必须打出的最少顺子数目以清空她所有的牌。

【输入】

* 第 1 行:整数 \(N\)

* 第 2 行到第 \(1+N\) 行:第 \(i+1\) 行包含 \(a_i\) 的值。

【输出】

* 第 1 行:Bessie 必须打出的最少顺子数目以清空她所有的牌。

【输入样例】

5
2
4
1
2
3

【输出样例】

6

【核心思想】

  1. 问题分析:给定 \(N\) 种牌面,每种有 \(a_i\) 张。每次可以打出一段连续牌面的顺子(从 \(i\)\(j\) 的所有牌各一张)。求清空所有牌所需的最少顺子数。这是一个贪心/差分累加问题,关键在于发现:当 \(a_i > a_{i-1}\) 时,多出的 \(a_i - a_{i-1}\) 张牌面 \(i\) 无法被前面的顺子覆盖,必须新开顺子。

  2. 算法选择

    • 贪心累加:遍历牌面从 \(1\)\(N\),若当前牌面数量 \(a_i > a_{i-1}\),则差值 \(a_i - a_{i-1}\) 即为必须以 \(i\) 为起点的新顺子数
  3. 关键步骤

    • 读取输入\(N\)\(a_1\)\(a_N\)
    • 初始化\(last = 0\)\(ans = 0\)
    • 贪心遍历\(i\)\(1\)\(N\)):
      • \(a_i > last\)\(ans \mathrel{+}= a_i - last\)(需要新开顺子)
      • \(last = a_i\)
    • 输出 \(ans\)
  4. 时间/空间复杂度

    • 时间复杂度:\(O(N)\)
    • 空间复杂度:\(O(1)\),仅需维护 \(last\)\(ans\)
  5. 贪心差分累加的核心思想

    • 顺子的连续性:一个顺子可以覆盖连续的一段牌面,若 \(a_i \le a_{i-1}\),则牌面 \(i\) 的所有牌都可以被延伸到 \(i\) 的已有顺子覆盖
    • 新增顺子的必要性:当 \(a_i > a_{i-1}\) 时,有 \(a_i - a_{i-1}\) 张牌面 \(i\) 无法被前面的顺子覆盖,必须以 \(i\) 为起点新开顺子
    • 最优性保证:每个差值都是必须的新开顺子数,累加即为全局最优
    • 适用于"连续覆盖 + 最小段数"类问题,核心是通过相邻差分发现必须的新开端点

【解题思路】

【算法标签】

普及 #贪心

【代码详解】

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

long long n;      // 输入数字的个数
long long a;      // 当前输入的数字
long long last;   // 记录前一个数字的值
long long ans=0;  // 存储最终结果

int main()
{
    // 输入数字的个数
    cin >> n;

    // 初始化前一个数字为0
    last = 0;

    // 遍历输入每个数字
    for (int i = 1; i <= n; i++)
    {
        cin >> a;

        // 如果当前数字大于前一个数字
        if (a > last)
        {
            // 将差值累加到结果中
            ans += (a - last);
        }

        // 更新前一个数字的值
        last = a;
    }

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

    return 0;
}

【运行结果】

5
2
4
1
2
3
6
posted @ 2026-08-28 15:57  团爸讲算法  阅读(2)  评论(0)    收藏  举报