其实是一道比较简单的暴力构造题(?)
但是因为没有往这方面去考虑所以就…
正如之前看到过 第一个造原子弹的国家才是真正厉害的 因为这是开辟一条从0到1的道路 后面的效仿者已知结果可行再去效仿就容易很多
这道题中最麻烦的地方在于如果产生进位会如何影响结果。
如果一个数末尾有连续k个9,那么对其加1后其数码之和会变化+1 - 9k
。
得知这个规律后就可以将$f(x) + f(x + 1) + …+f(x + k)$写成
$(k + 1) * f(x) + \frac{k * (k + 1)}{2} - 9 * cnt * k$
其中,$cnt$表示$f(x)…f(x +k)$其中会产生进位的数字的个数,$k$表示进位后会受到影响的9的个数。
当我们枚举n、k时,可以再枚举cnt(因为k已经给定,所以只有末尾数字dig影响cnt的值,因而枚举cnt即枚举dig)以及k,每次看是否存在一个$f(x)$满足等式条件。如果满足整除条件,就利用参数$cnt(dig)、k、f(x)$进行构造并更新答案。
每次构造先用$dig、k$填充低位,然后根据剩余的数码和构造高位。显然优先填够9,让总位数尽可能最小;当剩余数码不超过9时,直接填入该数码即可。有一个要注意的地方,因为枚举的$k$是末尾连续9的个数,所以当填充完$dig$和$k$个9后,下一位不能继续填9,而应该用8去填充作为分割。
cpp
1const int maxn = 200;
2const ll inf = 1ll << 62;
3ll ans[maxn][15];
4
5vector<int> vec;
6void make(int n, int k, int last, int num, int sum){
7 sum -= last; sum -= 9 * num;
8 vec.clear(); vec.push_back(last);
9 while(num--) vec.push_back(9);
10 if(sum < 0) return;
11
12 if(sum >= 8){
13 //作为分割符
14 vec.push_back(8);
15 sum -= 8;
16 }
17 while(sum > 9){
18 vec.push_back(9); sum -= 9;
19 }
20 if(sum) vec.push_back(sum);
21
22 ll res = 0;
23 int top = (int)vec.size(); if(top > 60) return;
24 for(int i = top - 1; i >= 0; i--){
25 res *= 10;
26 res += vec[i];
27 }
28 ans[n][k] = min(ans[n][k], res);
29}
30
31void sove(){
32 const int len = 20;
33 for(int n = 1; n <= 150; n++){
34 for(int k = 0; k <= 9; k++){
35 //f[x] * (k + 1) + (1 + ... + k) - 9 * 数的个数 * 每个数中末尾连续9的个数 = n。
36 //其中k是给定的,数的个数通过调节最后一位来控制,9的个数直接枚举.
37 int now = n - (k + 1) * k / 2;
38 for(int dig = 0; dig <= 9; dig++){
39 for(int num = 0; num < len; num++){
40 int cnt = max(0, dig + k - 10 + 1);
41 if((now + 9 * (num + 1) * cnt) % (k + 1) == 0){
42 make(n, k, dig, num, ((now + 9 * (num + 1) * cnt) / (k + 1)));
43 }
44 }
45 }
46 }
47 }
48}
49
50int main(){
51 for(int i = 0; i < maxn; i++) for(int j = 0; j < 15; j++) ans[i][j] = inf;
52 sove();
53
54// Fastin;
55 int t; scanf("%d", &t); while(t--){
56 int n, k; scanf("%d %d", &n, &k);
57 if(ans[n][k] - inf) printf("%lld\n", ans[n][k]);
58 else puts("-1");
59 }
60 return 0;
61}