[SCOI2013] 火柴棍数字 题解

前言

不想写题解,但是题解区没有简短易懂的做法。

题目链接:洛谷

题意简述

给定火柴棒拼成的数字 \(x\),你需要移动最多 \(k\) 根火柴棒,使数最大。

\(n\) 表示 \(x\) 的位数,\(1\leq n\leq500\)

题目分析

如果得知了最终的数 \(y\),怎么计算最小移动次数?

首先两者火柴棒根数必须相等。设 \(t\) 表示两个数按火柴棒异或后的火柴棒的数量(即 \(x\) 中某一个位置上有一根火柴棒,但在 \(y\) 中没有,反之亦然),\(t\) 显然是偶数,那么 \(\frac{t}{2}\) 就是最少移动次数。可以暴力打表找规律了。

\(y\) 相较于 \(x\) 高位多出的那部分很有规律,必定是一个或零个 \(7\) 后跟若干个 \(1\)。这样想,如果确定了这部分火柴棒总根数,每次就尽量在高位摆 \(1\),让 \(y\) 尽量长。多出来一根的话,把最高位的 \(1\) 变成 \(7\)

枚举多出部分的火柴棒根数,这部分按火柴棒异或后的数量,就是枚举的火柴棒根数。需要检查剩下部分有没有合法方案。

进一步明确问题,设枚举的根数为 \(i\),总火柴棒根数为 \(\mathrm{tot}\)。用 \(\mathrm{tot}-i\) 根火柴棒,摆出长度为 \(n\) 的数字,设其和 \(x\) 按火柴棒异或后的根数为 \(t\),求是否能使 \(t+i\leq2k\)

这部分显然可以数位 DP 了。设 \(f_{i,j}\) 表示用 \(j\) 根火柴棒,摆出长度为 \(i\) 的数字,和 \(x\)\(i\) 位按火柴棒异或后根数的最小值。转移很简单。

最后输出方案贪心高位从大到小考虑是否存在合法方案即可。

时间复杂度在忽略对于本题来说一系列常数是 \(\mathcal{O}(n^2)\)

代码

按火柴棒异或打 \(10\times10\) 的表太傻了,把每个数字用到的火柴棒状压就行了。

#include <cstdio>

#define popc __builtin_popcount

const int N = 5e2 + 10;
const int inf = 0x3f3f3f3f;
const int TOT = 3510;
const int c[] = { 6, 2, 5, 5, 4, 5, 6, 3, 7, 6 };
const unsigned st[] = { 0b1011111, 0b0001100, 0b1111001, 0b1111100, 0b0101110,
                        0b1110110, 0b1110111, 0b1001100, 0b1111111, 0b1111110 };

inline int min(int a, int b) { return a < b ? a : b; }

int n, k;
char x[N];
short y[N];

bool _vf[N][TOT];
int f[N][TOT];

int dfs(int p, int tot) {
    // 剩下 tot 根木棍,diff 的最小值
    if (p == 0) return tot ? inf : 0;
    if (_vf[p][tot]) return f[p][tot];
    _vf[p][tot] = true;
    int &o = f[p][tot] = inf;
    for (int i = 0; i < 10; ++i)
        if (tot >= c[i])
            o = min(o, dfs(p - 1, tot - c[i]) + popc(st[y[p]] ^ st[i]));
    return o;
}

void output(int tot, int cur) {
    for (int p = n; p; --p) {
        for (int o = 9; ~o; --o) {
            if (tot < c[o]) continue;
            int t = dfs(p - 1, tot - c[o]) + popc(st[y[p]] ^ st[o]);
            if (t + cur <= k * 2) {
                putchar(o | 48), tot -= c[o], cur += popc(st[y[p]] ^ st[o]);
                break;
            }
        }
    }
}

signed main() {
    scanf("%s%d", x + 1, &k);
    while (x[++n]);
    --n;
    int tot = 0;
    for (int i = 1; i <= n; ++i) {
        y[i] = x[n - i + 1] ^ 48;
        tot += c[y[i]];
    }
    for (int i = min(k, tot); i >= 2; --i)
        if (dfs(n, tot - i) + i <= 2 * k) {
            int x = i;
            if (x & 1) putchar('7'), x -= 3;
            for (; x; x -= 2) putchar('1');
            output(tot - i, i);
            return 0;
        }
    dfs(n, tot);
    output(tot, 0);
    return 0;
}
posted @ 2025-06-13 20:13  XuYueming  阅读(40)  评论(0)    收藏  举报