题解 P1590 【失踪的7】

· · 题解

原来的题解因为叙说很云里雾里,代码过于追求简短而忽略可读性,现重写此篇题解。

我的思路是这样的:

首先对于给定的数的某一位,引入一个概念:贡献值。(自己创造的概念,并非官方)

设这一位所在位数为 x(从低位到高位数),上面的值为 v,那么这一位的贡献值:

做出了如上定义之后,一个不含 7 的数字对应的答案就是每一位的贡献值之和。

接下来以 n = 5482 为例(注意 n 中不可含有 7,含有 7 的情况稍后会处理),说明该算法是如何工作的。

首先我们运用位值原理,将 5482 拆分为 5000 + 400 + 80 + 2

那么实际上,1 \sim 5482 中所有 Pascal 数的总数,第一步就被我们拆分为:0 \sim 49995000 \sim 53995400 \sim 54795480 \sim 5481

不知道到了这里你会不会萌生这样一个困惑:你是不是多算了一个 0 呀?你是不是少算了一个 5482 呀?

事实上,0 是 Pascal 数,5482 也是 Pascal 数,少算一个,多算一个,最后统计出的数据仍然是准确的。

解答你的疑惑后,我们继续再来分步处理这些部分:

**考虑使用乘法原理**:$5 \times 9 \times 9 \times 9$。这就是$0 \sim 4999$ Pascal 数的数量了。 那么,$5000 \sim 5399$ Pascal 数的数量是多少呢?第一个数只能取 $5$,不用考虑(或者考虑为 $1$ 种情况,最后乘法时不影响);第二个数可以从 $0$ 取到 $3$,总共有 $4$ 种情况;第三个数 $9$ 种;第四个数 $9$ 种。 最后就是 $3 \times 9 \times 9$。 不知道你发没发现这样一个规律:分步考虑后,每一位(值为 $v$)对应区间内 Pascal 数的数量其实就是:$v$(从 $0$ 取到 $v - 1$ 共 $v$种情况),乘上后面位数数量的 $9$。也就应了刚刚那个**贡献值的第一个式子**。 现在你明白了吗?所谓贡献值其实就是每一位对应区间内 Pascal 数的数量。这个区间的映射关系,是通过拆分实现的。 到第三位开始情况有了变化:变化在于这一位上是 $8$,但这一位的取值范围不再是 $0 \sim 8$,**因为 $7$ 也是需要扣去的!!** 最后第一位就变成了 $v - 1$ 种情况,乘法原理时相应也需要是 $v - 1$ 与那么多 $9$ 相乘。应了刚刚**贡献值的第二个式子**。 第四位后面就没有位数了,所以直接就是 $v$,不用乘 $9$,也可以说是乘 $9^0$( $=1$ )。 最后把每一位的贡献值相加,自然就是最终结果啦。 --- 什么?结束了吗? 不要忘了,数里边含有 $7$ 的情况,我们还需要拯救一下! 事实上,只需要在事前加这样一个小特判:**从最高位往下扫,如果遇到 $7$,那么这一位变成 $6$,之后所有位都变成 $9$。** 事实上道理也很简单,举个简单例子便可说明:**$1 \sim 5700700$ 的所有 Pascal 数的数量其实和 $1 \sim 5699999$ 是一样的。毕竟,$5700000 \sim 5700700$ 的所有数都不是 Pascal 数。** --- 代码: ```cpp #include <bits/stdc++.h>//万能头 int T; std :: string s; //这道题推荐字符串存储,因为要取的是每一位的值,且不涉及到整个数字的数字运算 int main() { std :: scanf("%d", &T); while (T--) { //多组数据测试时推荐这么写! std :: cin >> s; //特判开始 for (int i = 0; i < s.length(); ++i) { if (s[i] == '7') { s[i] = '6';//先把这一位标记为6 for (int j = i + 1; j < s.length(); ++j) s[j] = '9';//之后所有标记为9 break; //跳出特判循环,这个 break 实际上很重要,不可省略 //因为 700700 要变成 699999 而不是 699699 } } //特判结束,正片开始 int ans = 0; //初始化ans for (int i = s.length() - 1, atr = 1; i >= 0; --i, atr *= 9) ans += atr * (s[i] - '0') - (s[i] - '0' > 7 ? atr : 0); //重头戏!s[i] - '0' 就是 i 这一位代表的数字。 //先根据贡献原理加贡献,然后再判断是否大于7 //如果大于 7,那么扣除 atr,总体也就相当于 (s[i] - '0' - 1) * atr,合理 //如果不大于 7,那么扣除 0,也就是不扣除。 std :: printf("%d\n", ans); //输出答案 } return 0; } ```