题解:洛谷 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
【核心思想】
-
问题分析:给定 \(N\) 种牌面,每种有 \(a_i\) 张。每次可以打出一段连续牌面的顺子(从 \(i\) 到 \(j\) 的所有牌各一张)。求清空所有牌所需的最少顺子数。这是一个贪心/差分累加问题,关键在于发现:当 \(a_i > a_{i-1}\) 时,多出的 \(a_i - a_{i-1}\) 张牌面 \(i\) 无法被前面的顺子覆盖,必须新开顺子。
-
算法选择:
- 贪心累加:遍历牌面从 \(1\) 到 \(N\),若当前牌面数量 \(a_i > a_{i-1}\),则差值 \(a_i - a_{i-1}\) 即为必须以 \(i\) 为起点的新顺子数
-
关键步骤:
- 读取输入:\(N\) 和 \(a_1\) 到 \(a_N\)
- 初始化:\(last = 0\),\(ans = 0\)
- 贪心遍历(\(i\) 从 \(1\) 到 \(N\)):
- 若 \(a_i > last\):\(ans \mathrel{+}= a_i - last\)(需要新开顺子)
- \(last = a_i\)
- 输出 \(ans\)
-
时间/空间复杂度:
- 时间复杂度:\(O(N)\)
- 空间复杂度:\(O(1)\),仅需维护 \(last\) 和 \(ans\)
-
贪心差分累加的核心思想:
- 顺子的连续性:一个顺子可以覆盖连续的一段牌面,若 \(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
浙公网安备 33010602011771号