一个不错的数位dp入门题
求区间
[a,b]
中满足各个数位之和是k的倍数且这个数本身也是k的倍数的数的个数
数据范围 :
1≤a,b≤231,1≤k≤10000
一个不错的数位dp入门题
感觉这题如果要想清楚的话,还是要明确的定义一下dp的状态
dp[pos][m][ms]
为前缀剩下的长度为pos,前缀
modk
为m,前缀的数位和
modk
为ms的时候,接在那样的前缀的后面使得总的数字能符合题意的数的个数
(如果不能理解的话,可以多读两遍
这个题还有一个问题就是如果你dp的第三维是按照k的大小开的话,需要 O(log10(231)×k2) 的空间复杂度,显然是开不下的
但是仔细想想,其实第三维完全不用开这么大,因为虽然我们存的是 modk 的值,但是题目范围内的数的数位和其实只有 floor(log10(231))×9≈90 ,所以第三维只要开到100就够了
其实这个题还有一个优化,只要k超过90,我们都可以 O(1) 时间内得出答案,留作习题(想一想,为什么
不管对不对,反正这就是我对数位dp的理解了(笑
我是代码的昏割线
#include<bits/stdc++.h>
using namespace std;
const int maxn = 11234;
int k;
int dp[12][maxn][108];
int dig[12];
int dfs(int pos,int mod,int mods,bool lim)
{
if(pos < 0) return mod==0 && mods==0;
int &ndp = dp[pos][mod][mods];
if(!lim && ndp!=-1) return ndp;
int ret = 0;
int bound = lim ? dig[pos] : 9;
for(int i=0; i<=bound; i++)
{
ret += dfs(pos-1,(mod*10+i)%k,(mods+i)%k,lim && i == bound);
}
if(!lim)
ndp = ret;
return ret;
}
int cal(int n)
{
int len = 0;
while(n)
{
dig[len++] = n % 10;
n /= 10;
}
return dfs(len-1,0,0,true);
}
int main()
{
int T;
int a,b;
scanf("%d",&T);
while(T-- && ~scanf("%d %d %d",&a,&b,&k))
{
memset(dp,-1,sizeof(dp));
printf("%d\n",cal(b)-cal(a-1));
}
return 0;
}