数位DP技巧:
[X, Y] → f(Y) - f(X - 1)
,f(N)
表示1 ~ N
中满足某种性质的个数。比如第一题计数问题;- 利用树的角度考虑,比如度的数量。
1、计数问题
算法思路:一定要分情况讨论
首先,题目要求在[a, b]
中0 ~ 9
这10
个数中分别出现的次数,那么我们先实现一个函数count(n, x):求出从 1 ~ n 中 x 出现的次数
,则在[a, b]
中x
出现的次数表示为cout(b, x) - count(a-1, x)
(类似于前缀和)。
对count(n, x)
函数,例如当x=1
时,则count(n, 1)
表示为从1 ~ n
中找出1
出现的次数。先求出1
在1 ~ n
中每一个位上出现的次数,则其总和即为count(n, 1)
。
例如有abcdefg
,则1
在第四位出现可以表示为1 <= xxx1yyy <= abcdefg
,即为表示在1 ~ abcdefg
之间有多少个数的形式是xxx1yyy
。分情况讨论:
- ① 若
xxx = 000 ~ abc - 1
时,xxx1yyy < abc1yyy <= abc1efg
,则yyy 可取 000 ~ 999
,总共有abc * 1000
种选法; - ② 若
xxx = abc
时:- 若
d < 1
即d = 0
时,则第四位为1
即abc1yyy
,但是abc1yyy > abc0efg
,超出了范围,总共有0
种选法; - 若
d = 1
时,即abc1yyy
,则yyy 只能取 000 ~ efg
,总共有efg + 1
种选法; - 若
d > 1
时,则abcdefg > abc1yyy
,故yyy
可取000 ~ 999
,共1000
种选法。
- 若
由以上,依次类推,可以求出1
在任意一位数上出现的次数,然后将每一个位上的1
出现的次数累加起来,就能得到1
在1 ~ n
位中整个出现的次数。依次类推,可以求出1 ~ 9
每一个数在1 ~ n
中整个出现的次数。
边界情况:
- 当枚举数字
0
的时候,对于②不存在前导零,不用考虑;对于①,只能从001
开始枚举。
# include <iostream>
# include <cmath>
using namespace std;
int dgt(int n) // 计算整数n有多少位
{
int res = 0;
while (n) ++ res, n /= 10;
return res;
}
int cnt(int n, int i) // 计算从1到n的整数中数字i出现多少次
{
int res = 0, d = dgt(n); // d是n的位数
for (int j = 1; j <= d; ++ j) // 计算从右到左第j位上数字i出现多少次
{
// l和r是第j位左边和右边的整数 (视频中的abc和efg); dj是第j位的数字
int p = pow(10, j - 1), l = n / p / 10, r = n % p, dj = n / p % 10;
// 计算第j位左边的整数小于l (视频中xxx = 000 ~ abc - 1)的情况
if (i) res += l * p;
// 如果i = 0, 左边高位不能全为0(视频中xxx = 001 ~ abc - 1)
if (!i && l) res += (l - 1) * p;
// 计算第j位左边的整数等于l,不能存在前导零 (视频中xxx = abc)的情况
if ( (dj == i) && (i || l) ) res += r + 1;
if ( (dj > i) && (i || l) ) res += p;
}
return res;
}
int main()
{
int a, b;
while (cin >> a >> b , a)
{
if (a > b) swap(a, b);
for (int i = 0; i <= 9; ++ i) cout << cnt(b, i) - cnt(a - 1, i) << ' ';
cout << endl;
}
return 0;
}
2、度的数量
题中引例的意思实质上是:找到区间[x,y]
中的某些数,这些数的B进制
(引例是B=2
二进制) 表示中仅包含k = 2
个1
,其余为0
。
假设我们需要求 [ 0 , N ] [0, N] [0,N]中满足某种性质的个数即数位DP技巧中的f[N]
,若数 N N N是 n n n位数,其表示为 a n − 1 、 a n − 2 、 . . . 、 a 0 a_{n-1}、a_{n-2} 、... 、a_0 an−1、an−2、...、a0。
- 一定要注意审题:这个数恰好等于 K 个互不相等的 B 的整数次幂之和。这句话的意思就表明了
K 个互不相等的 B 的整数次幂
前面的系数只能是0 or 1
!!!所以在下面图中,比如结点 0 0 0 ~ a n − 1 − 1 a_{n-1}-1 an−1−1 仅有两个分支0 or 1
,大于1
的分支都不合法!- 这里 N N N是已经转换为了 B B B进制下的数,比如: 19 19 19,在 3 3 3进制为 19 = ( 201 ) 3 19 =(201)_3 19=(201)3,即 19 = 2 × 3 2 + 0 × 3 1 + 1 × 3 0 19 = 2\times3^2+0\times3^1+1\times3^0 19=2×32+0×31+1×30。
组合数的计算公式 C a b = C a − 1 b − 1 + C a − 1 b C_a^b=C_{a-1}^{b-1}+C_{a-1}^b Cab=Ca−1b−1+Ca−1b。代码中使用f[a][b]
来表示:从a
中选择b
个数的方案数。
#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;
const int N = 35; // 1 ≤ X ≤ Y ≤ 2^31−1,所以最多31位
int K, B;
int f[N][N]; // f[a][b]:从a个数中选择b个数的方案数
void init() // 预处理f[a][b]
{
for (int i = 0; i < N; i ++ )