题目描述
火柴棍是表示数字的理想工具。题目给出了用火柴棍表示十进制数字的方式(与普通闹钟显示数字的方式相同)。每个数字0-9所需的火柴棍数量如下:
| 数字 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
|---|---|---|---|---|---|---|---|---|---|---|
| 根数 | 6 | 2 | 5 | 5 | 4 | 5 | 6 | 3 | 7 | 6 |
给定 nnn 根火柴棍,我们需要找出使用所有火柴棍能够组成的最小和最大数字。要求两个数字都必须是正数且不能有前导零。
输入格式
- 第一行:测试用例数量 TTT(最多 100100100 个)
- 每个测试用例一行:整数 nnn(2≤n≤1002 \leq n \leq 1002≤n≤100),表示火柴棍数量
输出格式
每个测试用例输出一行:最小数字和最大数字,用空格分隔
题目分析
问题难点
- 最小数字:需要位数尽可能少,且在高位使用尽可能小的数字
- 最大数字:需要位数尽可能多,且在高位使用尽可能大的数字
- 约束条件:数字必须为正数且无前导零
- 火柴棍分配:必须恰好使用所有火柴棍
解题思路
最小数字的求解
我们使用动态规划方法来解决最小数字问题:
- 定义 dp[i]dp[i]dp[i] 为使用 iii 根火柴棍能组成的最小数字(字符串形式)
- 初始化:dp[0]=""dp[0] = ""dp[0]=""(空字符串),其他位置初始化为不可达状态
- 状态转移:对于每个状态 iii,尝试添加数字 ddd(000-999),如果 i−cost[d]≥0i - cost[d] \geq 0i−cost[d]≥0 且 dp[i−cost[d]]dp[i - cost[d]]dp[i−cost[d]] 可达,则更新 dp[i]dp[i]dp[i]
- 特别注意:要避免前导零,即当 dp[i−cost[d]]dp[i - cost[d]]dp[i−cost[d]] 为空时,不能添加数字 000
最大数字的求解
最大数字的构造相对简单,采用贪心策略:
- 为了得到最大数字,我们需要尽可能多的位数
- 数字 111 只需要 222 根火柴棍,是使用火柴棍最少的数字
- 如果 nnn 是偶数:全部用数字 111,得到 n2\frac{n}{2}2n 位的数字
- 如果 nnn 是奇数:先用 333 根火柴棍组成数字 777,剩下的用数字 111,得到 1+n−321 + \frac{n-3}{2}1+2n−3 位的数字
这种策略能保证位数最多,且在高位使用尽可能大的数字。
算法复杂度分析
- 最小数字:动态规划的时间复杂度为 O(n×10)O(n \times 10)O(n×10),即 O(n)O(n)O(n)
- 最大数字:直接构造,时间复杂度 O(1)O(1)O(1)
- 总体复杂度:O(T×n)O(T \times n)O(T×n),对于 n≤100n \leq 100n≤100 完全可行
代码实现
// Matchsticks
// UVa ID: 12172
// Verdict: Accepted
// Submission Date: 2025-11-24
// UVa Run Time: 0.000s
//
// 版权所有(C)2025,邱秋。metaphysis # yeah dot net
#include <bits/stdc++.h>
using namespace std;
// 每个数字0-9所需的火柴棍数量
vector<int> cost = {6, 2, 5, 5, 4, 5, 6, 3, 7, 6};
// 获取使用n根火柴棍能组成的最小数字
string getMinNumber(int n) {
// dp[i]表示使用i根火柴棍能组成的最小数字
vector<string> minSticks(n + 1, "#");
minSticks[0] = "";
for (int i = 1; i <= n; i++) {
for (int d = 0; d <= 9; d++) {
if (i - cost[d] >= 0 && minSticks[i - cost[d]] != "#") {
// 避免前导零:当minSticks[i-cost[d]]为空时,不能加0
if (minSticks[i - cost[d]] == "" && d == 0) continue;
string newNum = minSticks[i - cost[d]] + to_string(d);
// 更新最小数字:先比较位数,位数相同再比较数值
if (minSticks[i] == "#" ||
newNum.length() < minSticks[i].length() ||
(newNum.length() == minSticks[i].length() && newNum < minSticks[i])) {
minSticks[i] = newNum;
}
}
}
}
return minSticks[n];
}
// 获取使用n根火柴棍能组成的最大数字
string getMaxNumber(int n) {
// 偶数:全部用1(2根火柴棍)
if (n % 2 == 0) return string(n / 2, '1');
// 奇数:先用3根组成7,剩下的用1
else return "7" + string((n - 3) / 2, '1');
}
int main() {
int t;
cin >> t;
while (t--) {
int n;
cin >> n;
cout << getMinNumber(n) << " " << getMaxNumber(n) << endl;
}
return 0;
}
样例分析
以题目样例为例:
输入:
4
3
6
7
15
输出:
7 7
6 111
8 711
108 7111111
- n=3n = 3n=3:最小和最大都只能是数字 777(需要 333 根)
- n=6n = 6n=6:最小是 666,最大是 111111111(三个 111)
- n=7n = 7n=7:最小是 888,最大是 711711711(777 需要 333 根,111 需要 222 根 × 222 = 444 根)
- n=15n = 15n=15:最小是 108108108,最大是 711111171111117111111
总结
本题的关键在于:
- 最小数字使用动态规划,确保无前导零且数值最小
- 最大数字使用贪心策略,优先保证位数最多
- 充分利用了数字 111 和 777 的火柴棍特性来构造最优解
这种方法既保证了正确性,又具有较高的效率,能够很好地解决该问题。


5618

被折叠的 条评论
为什么被折叠?



