数位DP
问题
求区间 [ L , R ] [L, R] [L,R]中满足条件的数有多少个, 0 ≤ L ≤ R 0 \le L \le R 0≤L≤R,该条件与数位有关,比如不包含数字 4 4 4。
思路
考虑函数
c
a
l
(
n
)
cal(n)
cal(n)表示区间
[
0
,
n
]
[0, n]
[0,n]中满足条件的数的个数,那么区间
[
L
,
R
]
[L, R]
[L,R]中满足条件的数的个数为
c
a
l
(
R
)
−
c
a
l
(
L
−
1
)
cal(R) - cal(L-1)
cal(R)−cal(L−1)。
然后用动态规划的方法求
c
a
l
(
n
)
cal(n)
cal(n)。求出
n
n
n的每一位数字以及长度
l
e
n
len
len,用数组
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j]表示长度为
i
i
i首位为
j
j
j的满足条件的数的个数,那么
d
p
[
i
]
[
0
]
dp[i][0]
dp[i][0]表示长度为
i
i
i的满足条件的后缀的个数,而
d
p
[
l
e
n
]
[
0
]
dp[len][0]
dp[len][0]表示所有长度小于
l
e
n
len
len的满足条件的数的个数,令
a
n
s
=
d
p
[
l
e
n
]
[
0
]
ans = dp[len][0]
ans=dp[len][0]。
最后求出所有长度为
l
e
n
len
len的满足条件的数的个数。对于
n
n
n从高位到低位,计算所有最高的
(
i
−
1
)
(i-1)
(i−1)位与
n
n
n相同且第
i
i
i位比
n
n
n小的满足条件的数的个数,设
n
n
n的第
i
i
i位数字为
d
i
d_i
di,则
a
n
s
=
a
n
s
+
∑
0
d
i
−
1
d
p
[
i
]
[
d
i
]
ans = ans + \sum^{d_i-1}_0 dp[i][d_i]
ans=ans+∑0di−1dp[i][di],如果
n
n
n从第
i
i
i位开始不满足条件就退出循环。若
n
n
n本身满足条件,则
a
n
s
=
a
n
s
+
1
ans = ans + 1
ans=ans+1。那么
c
a
l
(
n
)
=
a
n
s
cal(n) = ans
cal(n)=ans。
时间复杂度
O ( log n ) O(\log n) O(logn)。实际上为 n n n的位数,而 n n n的位数可以表示为 lg n \lg n lgn,即 log n log 10 \frac {\log n}{\log 10} log10logn, log \log log默认为以 2 2 2为底的对数。
测试
模板
#include <iostream>
using namespace std;
#include <cstdio>
#include <cstring>
typedef long long LL;
const int N = 20; // the maximal number of digits
LL dp[N][10]; // dp[i][j] is the amount of numbers with i digits and first digit j
int nums[N]; // the digits of n
/**
* @param n: the maximal number
* @return: the amount of numbers from 0 to n
*/
LL cal(LL n) {
int len = 0; // the length of n
// nums[i] is the i-th digit of n from right to left
while (n > 0) {
nums[++len] = n % 10;
n /= 10;
}
memset(dp, 0, sizeof(dp));
for (int i = 1; i <= len; ++i) {
for (int j = 0; j <= 9; ++j) {
/*
update: dp[i][j]
*/
}
}
LL ans = dp[len][0]; // the numbers with length shorter than n
dp[len][0] = 0;
int pos; // the position of the digits of n
for (pos = len; pos >= 1; --pos) {
for (int j = 0; j < nums[pos]; ++j) {
/*
condition: continue
*/
ans += dp[pos][j]; // the numbers less than n
}
/*
condition: break
*/
if (dp[pos][nums[pos]] == 0) break; // no more numbers
}
if (pos == 0) ++ans; // n
return ans;
}