题目大意
给出 \(N\) , 求出最少的数满足每个数位上只为 \(1, 2, 3\) 且所有数的和为 \(N\)
题解
首先, 我们可以有一个结论: 对于任意两个数位, 当 \(i > j\), 则数位大于等于 \(i\) 的数的个数不多于数位大于等于 \(j\) 的数的个数。
证明: 若一个数, 它的数位大于等于 \(i\) , 因为 \(i > j\) , 所以它的数位一定大于等于 \(j\) 。
有了这个性质, 接下来再考虑题怎么做。
看到这里的最少个数, 比较好想到的应该是二分, 因为它貌似好像有单调性。 但仔细观察后你会发现, 这东西压根就不是单调的。
比如 \(N=1\) 时, 只能用一个数凑出来, 多一个少一个都不行, 那么我们就只好换一种方法了。
既然刚才推出来的结论和位数有关, 那我们是不是该用这个结论做些什么呢? 比如, 用每位数上是几去做一个 DP 。
首先考虑状态, 既然我们要个数尽量少, 那么就需要每一个数位状态上的数尽量少, 而且我们还要考虑可以进位的情况, 所以定义状态为 \(dp[i][j]\) 表示在 \(i - 1\) 位进位小于等于 \(9\) 可以使 \(i\) 位及之前的数与 \(N\) 中相同位置的数相同 , 且 \(i\) 位为 \(j\) 的时候的最少个数。 我们考虑可以通过全填 \(1\) 来算出能不能用至少 \(k\) 个 \(1~3\) 来凑出 \(M\) 。而我们也可以用 \(\lceil M/3\rceil\) 来算出没有限制时最少要用多少个 \(1~3\) 来凑出 \(M\) 。 那么, 就可以得到函数:
long long f (int n//要凑出的数, int h//至少要用的数) {
if (h > n) {//如果h全填1都大于n了, 那不可能填出来
return INF;
}
if (n == 0) {//如果n是0就可以直接凑出来, 其实这个判断根本没必要, 可以不加
return 0;
}
return max ((n + 2) / 3, h);//求出至少要多少可以凑出来n
}
对于每个数位, 我们都可以通过前一个数还剩下的与自己本身这一位规定组成一个数, 这就是我们要在个数大于等于上一次的个数这一限制下需要凑出的数。
最后, 我们就可以定义初始状态了。
因为 \(N\) 的最高位没有进位, 所以对于 \(N\) 的最高位 \(x\) , 所有大于 \(x\) 的状态都不存在, 设为 \(INF\) , 其它的直接用 \(f()\) 构造。
代码:
#include <cstdio>
#include <algorithm>
using namespace std;
#define MAXN 18
#define INF 0x3f3f3f3f
int s[MAXN + 5];
long long s1[MAXN + 5][10 + 5];
long long f (int n, int h) {
if (h > n) {
return INF;
}
return max ((n + 2) / 3, h);
}
int main () {
int t;
scanf ("%d", &t);
while (t --) {
long long n;
int tot = 0;
scanf ("%lld", &n);
while (n) {
s[++tot] = n % 10;
n /= 10;
}
for (int i = s[tot]; i >= 0; i --) {
s1[tot][i] = f (i, 0);
}
for (int i = 9; i > s[tot]; i --) {
s1[tot][i] = INF;
}
for (int i = tot - 1; i >= 1; i --) {
for (int k = 0; k <= s[i]; k ++) {
s1[i][k] = INF;
for (int j = 0; j <= s[i + 1]; j ++) {
s1[i][k] = min (s1[i][k], f ((s[i + 1] - j) * 10 + k, s1[i + 1][j]));
}
for (int j = s[i + 1] + 1; j <= 9; j ++) {
s1[i][k] = min (s1[i][k], f ((s[i + 1] + 10 - j) * 10 + k, s1[i + 1][j]));
}
}
for (int k = s[i] + 1; k <= 9; k ++) {
s1[i][k] = INF;
for (int j = 0; j < s[i + 1]; j ++) {
s1[i][k] = min (s1[i][k], f ((s[i + 1] - j - 1) * 10 + k, s1[i + 1][j]));
}
for (int j = s[i + 1]; j <= 9; j ++) {
s1[i][k] = min (s1[i][k], f ((s[i + 1] + 10 - j - 1) * 10 + k, s1[i + 1][j]));
}
}
}
printf ("%lld\n", s1[1][s[1]]);
}
}