题目大意:
令 f ( i ) f(i) f(i)为 i i i的数位和,问你一个区间 [ L , R ] [L,R] [L,R]中 f ( i ) f(i) f(i)出现最多的 f ( i ) f(i) f(i)是多少,若有多解,输出任何一个.
1 ≤ L ≤ R ≤ 1 e 18 1 \leq L \leq R \leq 1e18 1≤L≤R≤1e18
题目思路:
由于范围太大,肯定是数位dp了。但是发现套数位dp模板并不好用。
①一种暴力的套模板方法:
按照套路:
d
p
状
态
为
(
前
缀
长
度
,
前
缀
数
位
和
,
是
否
顶
到
L
,
是
否
顶
到
R
)
dp状态为(前缀长度,前缀数位和,是否顶到L,是否顶到R)
dp状态为(前缀长度,前缀数位和,是否顶到L,是否顶到R)时符合递推出口条件的数的个数
我们发现 f ( i ) , i ∈ [ L , R ] f(i),i \in [L,R] f(i),i∈[L,R]的不同取值为 l o g 10 R ∗ 9 log_{10}R * 9 log10R∗9.所以我们可以暴力跑 l o g 10 R ∗ 9 log_{10}R * 9 log10R∗9遍,每一遍的递归出口为 [数位和 = = i == \ i == i].
所以我们可以得到 f ( i ) f(i) f(i)的每种可能取值的结果的出现次数,然后取最大值即可.
这种时间复杂度为: O ( ( l o g 10 R ) 3 ∗ 9 ∗ 9 ∗ 9 ) O((log_{10}R)^3*9*9*9) O((log10R)3∗9∗9∗9)
其中:
l
o
g
10
R
∗
9
log_{10}R * 9
log10R∗9来自枚举
f
(
i
)
f(i)
f(i)的可能取值
l
o
g
10
R
∗
9
log_{10}R * 9
log10R∗9来自数位dp时的数位和
l
o
g
10
R
log_{10}R
log10R来自数位dp时的数位和
9
9
9来自数位dp时填数的复杂度.
所以十进制下长度为 18 18 18范围内的数基本是该种方法的极限。
②大佬的更优秀的递推数位dp解法
还是设:
d
p
状
态
为
(
前
缀
长
度
,
前
缀
数
位
和
,
是
否
顶
到
L
,
是
否
顶
到
R
)
dp状态为(前缀长度,前缀数位和,是否顶到L,是否顶到R)
dp状态为(前缀长度,前缀数位和,是否顶到L,是否顶到R)时[前缀数位和]的出现次数.
但是我们考虑使用递推的方法,因为我们发现以往的记忆化搜索方法往往需要到 d p dp dp出一个完整的结果才能计算答案。但是在这我们可以以递推刷表的方式从前往后转移答案。
这里放一个标准板子:
string x , y;
int dp[8][59][2][2];
int countBalls(int lowLimit, int highLimit) {
x = to_string(lowLimit);
y = to_string(highLimit);
int n = y.size() , m = n * 9;
x = string(n - x.size() , '0') + x;
dp[0][0][1][1] = 1;
for (int i = 0 ; i < n ; i++){
for (int j = 0 ; j <= m ; j++){
for (int k = 0 ; k <= 1 ; k++){
for (int l = 0 ; l <= 1 ; l++){
if (dp[i][j][k][l] == 0) continue;
int low = (k ? x[i] - '0' : 0);
int up = (l ? y[i] - '0' : 9);
int val = dp[i][j][k][l];
for (int d = low ; d <= up ; d++){
int nj = j + d;
int nk = (k && d == low);
int nl = (l && d == up);
dp[i + 1][nj][nk][nl] += val;
}
}
}
}
}
int ans = 0;
for (int i = 1 ; i < 50 ; i++){
int res = 0;
for (int j = 0 ; j <= 1 ; j++)
for (int k = 0 ; k <= 1 ; k++)
res += dp[n][i][j][k];
ans = max (ans , res);
}
return ans;
}
心得:
如何初始化string. = string(长度,填充字符);