UVa 12172 Matchsticks

题目描述

火柴棍是表示数字的理想工具。题目给出了用火柴棍表示十进制数字的方式(与普通闹钟显示数字的方式相同)。每个数字0-9所需的火柴棍数量如下:

数字0123456789
根数6255456376

给定 nnn 根火柴棍,我们需要找出使用所有火柴棍能够组成的最小最大数字。要求两个数字都必须是正数且不能有前导零。

输入格式

  • 第一行:测试用例数量 TTT(最多 100100100 个)
  • 每个测试用例一行:整数 nnn2≤n≤1002 \leq n \leq 1002n100),表示火柴棍数量

输出格式

每个测试用例输出一行:最小数字和最大数字,用空格分隔

题目分析

问题难点

  1. 最小数字:需要位数尽可能少,且在高位使用尽可能小的数字
  2. 最大数字:需要位数尽可能多,且在高位使用尽可能大的数字
  3. 约束条件:数字必须为正数且无前导零
  4. 火柴棍分配:必须恰好使用所有火柴棍

解题思路

最小数字的求解

我们使用动态规划方法来解决最小数字问题:

  • 定义 dp[i]dp[i]dp[i] 为使用 iii 根火柴棍能组成的最小数字(字符串形式)
  • 初始化:dp[0]=""dp[0] = ""dp[0]=""(空字符串),其他位置初始化为不可达状态
  • 状态转移:对于每个状态 iii,尝试添加数字 ddd000-999),如果 i−cost[d]≥0i - cost[d] \geq 0icost[d]0dp[i−cost[d]]dp[i - cost[d]]dp[icost[d]] 可达,则更新 dp[i]dp[i]dp[i]
  • 特别注意:要避免前导零,即当 dp[i−cost[d]]dp[i - cost[d]]dp[icost[d]] 为空时,不能添加数字 000
最大数字的求解

最大数字的构造相对简单,采用贪心策略

  • 为了得到最大数字,我们需要尽可能多的位数
  • 数字 111 只需要 222 根火柴棍,是使用火柴棍最少的数字
  • 如果 nnn 是偶数:全部用数字 111,得到 n2\frac{n}{2}2n 位的数字
  • 如果 nnn 是奇数:先用 333 根火柴棍组成数字 777,剩下的用数字 111,得到 1+n−321 + \frac{n-3}{2}1+2n3 位的数字

这种策略能保证位数最多,且在高位使用尽可能大的数字。

算法复杂度分析

  • 最小数字:动态规划的时间复杂度为 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 100n100 完全可行

代码实现

// 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,最大是 711711711777 需要 333 根,111 需要 222 根 × 222 = 444 根)
  • n=15n = 15n=15:最小是 108108108,最大是 711111171111117111111

总结

本题的关键在于:

  1. 最小数字使用动态规划,确保无前导零且数值最小
  2. 最大数字使用贪心策略,优先保证位数最多
  3. 充分利用了数字 111777 的火柴棍特性来构造最优解

这种方法既保证了正确性,又具有较高的效率,能够很好地解决该问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值