题目链接:
https://acm.hdu.edu.cn/showproblem.php?pid=4389
题目大意:
给出一个函数 \(f\)
int f ( int x ) {
if ( x == 0 ) return 0;
return f ( x / 10 ) + x % 10;
}
\(T\) 组测试,每组给两个数 \(a\) 和 \(b\),求出在 \([a, b]\) 范围中的数 \(x\),满足 \(x\) % \(f(x)\) == 0 这个等式的有多少个。
思路:
\(f(x)\) 函数的返回值就是 \(x\) 所有位置上的数字和,显然,通过暴力枚举区间内每个数去计算满足条件的数字数量的时间复杂度太高。尝试用其它的方法来求解。
所有位数之和最大只有 81,所以考虑枚举所有位数之和来。
定义一个四维的 \(dp\) 数组 \(dp[pos][sum][mod][res]\),表示枚举到第 \(pos\) 位,所有位数之和为 \(sum\),对 \(mod\) 取模,余数为 \(res\) 的数有多少个。通过记忆化搜索的方式求值。
在搜索的过程中,要考虑每一位上的值有没有限制,如果前面都相等,例如求的数是 1234,枚举到 122X,那么 X 可以从 0 取到 9,如果是 123X,那 X 最大只能到 4 了。所以 \(dfs\) 的过程中还要再多一个参数,记录当前求的这一个状态有没有限制。
刚开始枚举的是最高位,从最高位依次枚举到最低位,结束的状态就是当枚举完每一位,若位数之和就是取模的数,且余数为 0,说明取模之后结果为 0,那答案就 +1,否则不加。
当某个状态已经被记录了,那么直接返回该值,不再重复计算,降低时间复杂度。
从某个状态转移到下一个状态,\(sum\) 要加上这个数,取模不变,余数从 \(res\) 变成了 \((res * 10 + i)\) % \(mod\),即加入新的数之后取模留下的余数。
代码:
#include <bits/stdc++.h>
using namespace std;
const int N = 11, M = 82;
int T, a, b, d[N], dp[N][M][M][M];
int dfs(int pos, int sum, int mod, int res, bool f){
if (pos == 0){
if (sum == mod && res == 0) return 1;
else return 0;
}
if (!f && dp[pos][sum][mod][res] != -1) return dp[pos][sum][mod][res];
int ans = 0, c = !f ? 9 : d[pos];
for (int i = 0; i <= c; i ++ )
ans += dfs(pos - 1, sum + i, mod, (res * 10 + i) % mod, f && i == d[pos]);
if (!f) dp[pos][sum][mod][res] = ans;
return ans;
}
int solve(int x){
int len = 0;
while (x){
d[++len] = x % 10;
x /= 10;
}
int ans = 0;
for (int i = 1; i <= 81; i ++ )
ans += dfs(len, 0, i, 0, true);
return ans;
}
int main(){
memset(dp, -1, sizeof dp);
cin >> T;
for (int i = 1; i <= T; i ++ ){
cin >> a >> b;
printf("Case %d: %d\n", i, solve(b) - solve(a - 1));
}
return 0;
}