[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;
}
本文作者:XuYueming,转载请注明原文链接:https://www.cnblogs.com/XuYueming/p/18927601。
若未作特殊说明,本作品采用 知识共享署名-非商业性使用 4.0 国际许可协议 进行许可。

浙公网安备 33010602011771号