628D - Magic Numbers(数位dp)
题意:给你一个区间[a,b]已知,a和b位数长度相同且都小于2000,让你求这个区间内所有的d-magic数,d-magic数就是这个数从高位往低位数,也就是从左往右数,偶数位的数都是d且奇数位的数不为d且这个数要能被m整除。
解法:因为这个a,b实际长度2000以内,很大,我们一般考虑是求[0,b]-[0,a-1]的数,因为a-1不好求,所以还是求[0,a]内的,然后特判一个a是否符合。而又因为已知a,b的实际长度相同,所以我们可以不用考虑位数小于a和b的数,换句话说就是不用考虑前导0产生的数。比如求[1234,4000]这个区间的数就可以理解为求[1000,4000]-[1000,1234]。然后对于一个区间要求它所有的d-magic数,我们可以用试填的方法判断出所有这个区间内的数,从高位开始,limit用来判断这个位数欲填的数的满足条件,当limit=1时,说明它的上一位已经试填了这个数的上界,到它时,它的上界只能是这个数这个位数的上界。而limit=0时,说明这个数上一位没有填上界,那么这个数可以选择0-9任意的数。我们可以发现比如a=1345,当我们从高位填到4这个位置时,当试填的数i<4时也就是对于下一位来说limit=0时,它们的所有总数都是一致的。
我感觉这个就是暴力枚举的做法加了一点记忆化,比如[0,a]a=1345,它枚举到后面位数时,比如到第二位a这个位的数字是3时,它在考虑枚举这个数字<3时转化为了一个普遍问题,就是对于所有这个位<3时也就是这个位limit=0时,同时余数为一个数的这个dp记忆下来,之后在遇到就直接用了。
#include<bits/stdc++.h>
using namespace std;
#define LL long long
char sa[2005],sb[2005];
int wei[2005];
LL f[2005][2005];
const LL mod=1e9+7;
int n;
int m,d;
int dfs(int limit,int pos,int yu)
{
if(pos==n+1) return yu==0;
if(!limit&&f[pos][yu]!=-1) return f[pos][yu];
int up=limit?wei[pos]:9;
LL ans=0;
for(int i=0; i<=up; i++)
{
if(pos%2==0&&i!=d) continue;
if(pos%2==1&&i==d) continue;
ans=(ans%mod+dfs((limit&&i==wei[pos]),pos+1,(10*yu+i)%m)%mod)%mod;
}
return limit?ans:f[pos][yu]=ans;
}
int tepan()
{
int sum=0;
for(int i=1; i<=n; i++)
{
if(i%2==0)
{
if(wei[i]!=d) return 0;
}
else if(wei[i]==d) return 0;
sum=(sum*10+wei[i])%m;
}
if(sum!=0) return 0;
else return 1;
}
int main()
{
memset(f,-1,sizeof(f));
scanf("%d%d",&m,&d);
scanf("%s%s",sa+1,sb+1);
n=strlen(sa+1);
LL ans=0;
for(int i=1; i<=n; i++)
wei[i]=sb[i]-'0';
ans+=dfs(1,1,0);
for(int i=1; i<=n; i++)
wei[i]=sa[i]-'0';
if(tepan()) ans++;
ans=(ans-dfs(1,1,0)+mod)%mod;
printf("%lld",ans);
}