这一道题目弄了两天,终于对数位dp有了新的理解.
参考了一个大佬的代码大佬的博客
题目大意:
给定,,a,b,k.找出所有满足在a<=x<=b条件的整数,要求能被k整除,数位之和也能被k整除。
分析
递推题目的第一步大致相同,设f(x)表示不超过x的非负整数中满足条件的个数。那么我们所求答案便是f(b)-f(a-1);问题的关键就在于如何计算f函数.假设我们需要计算f(12345)就是要数一数在0~12345范围内有多少满足要求的数,由于题目中给的范围很大,一一枚举是不可能的,我们得想办法"取巧",白书上介绍的方法是使用加法原理,分段求和。
每一段都可以使用一个包含若干固定前缀和用星号表示的"后缀"的模板。其中星号表示可以人选用一个数字。
设f(d,m1,m2)表示 共d个数字,其中各个数字之和除以k的余数为m1,这些数字组成的整数除以k的余数为m2;
递推公式
f(d,m1,m2)={sum(f(d-1,(m1-x)mod k,(m2-10d-1)mod k|x=0,1,2,……9};
#include<iostream>
#include<algorithm>
#include<string.h>
#define maxn 105
using namespace std;
typedef long long ll;
ll num[20];
int di[15], a, b, k, n;
int d[15][maxn][maxn], vis[15][maxn][maxn];
void pow_10() {
num[0]=1;
for (int i = 1; i <= 15; i++)
num[i]=num[i-1]*10;
}
int dp(int cur, int m1, int m2, bool judge) {
int &ans = d[cur][m1][m2];
if (vis[cur][m1][m2]&&!judge) return ans; //记忆搜索如果是上届不可以按照平常方式一样返回
int ans0=0; //备用
vis[cur][m1][m2] = 1;
if (cur == 0) { //出口
if (m1 == 0 && m2 == 0)
return ans=1;
return ans=0;
}
for (int i = 0; i < (judge ? di[cur] : 10); i++) { //当上一位触及到上界时,该位置也必须进行约束
ans0+= dp(cur - 1, (m1 + i) % k, (m2 + i * num[cur - 1]) % k, false);
}
if (judge) //触及上界,单独处理
ans0+= dp(cur - 1, (m1 + di[cur]) % k, (m2 + di[cur] * num[cur - 1]) % k, true);
if (!judge) ans = ans0; //如果是上界因为单独处理,需要判断
return ans0;
}
int solve(int nu) {
if (nu == 0) return 1;
n = 0;
while (nu) {
di[++n] = nu % 10;
nu /= 10;
}
return dp(n, 0, 0, true);
}
int main() {
int t;
cin >> t;
while (t--) {
memset(d, 0, sizeof(d));
memset(vis, 0, sizeof(vis));
cin >> a >> b >> k;
pow_10();
if (k > 100) { cout << 0 << '\n'; continue; }
int bb = solve(b);
int aa = solve(a-1);
cout << solve(b) - solve(a - 1) << '\n';
}
return 0;
}