[数位dp][洛谷P6371] [COCI2006-2007#6] V
数位dp。
设 \(dp[pos][num]\) 表示枚举到第 \(pos\) 位,并且前面的位数模 \(X\) 为 \(num\) 时,使得最终能被 \(X\) 整除的数的个数。那么容易进行状态转移。
但是发现 \(X\) 达到 \(10^{11}\),\(dp\) 数组开不下,我们可以使用 map 来代替 \(dp\) 数组。但直接这样写将会 MLE 1个点。容易想到保存的无用的状态太多,很多情况答案都是为0的,我们考虑进行剪枝。
设当前枚举到第 \(pos\) 位,并且前面的位数模 \(X\) 为 \(num\) ,设 \(temp=(X-num\times10^{pos}) \mod X\),设能使用的最大的数字为 \(maxnum\),那么若剩下的 \(pos\) 位数字全由 \(maxnum\) 组成,但仍比 \(temp\) 小,说明这样DFS下去必然不可能使得模数为0,直接剪枝即可。
AC代码:
#include <iostream>
#include <algorithm>
#include <cstring>
#include <string>
#include <cstdio>
#include <vector>
#include <map>
using namespace std;
#define RG register int
#define LL long long
bool cnt[10];
int a[20];
LL Num[10][11],Ten[11];
char str[20];
map<LL,LL> dp[12];
LL Div;
int maxnum;
LL DFS(int pos,bool limit,bool lead,LL num){
if(!pos) return num==0?1:0;
if(!limit && !lead && dp[pos].count(num)) return dp[pos][num];
int up=limit?a[pos]:9;
LL Res=0;
LL temp=num*(Ten[pos]%Div)%Div,temp2;
temp2=((Div-temp)%Div+Div)%Div;
temp=Num[maxnum][pos];
if(temp<temp2){
if(!limit && !lead) dp[pos][num]=Res;
return Res;
}
for(int i=0;i<=up;++i)
if(cnt[i] || (i==0 && lead))
Res+=DFS(pos-1,limit && i==up,lead && i==0,(num*10LL+i)%Div);
if(!limit && !lead) dp[pos][num]=Res;
return Res;
}
LL Solve(LL x){
int pos=0;
while(x){a[++pos]=x%10;x/=10;}
return DFS(pos,true,true,0);
}
int main(){
Ten[0]=1;
for(RG i=1;i<11;++i)
Ten[i]=Ten[i-1]*10LL;
for(RG x=1;x<10;++x)
for(RG i=1;i<11;++i)
Num[x][i]=Num[x][i-1]*10LL+x;
LL L,R;
scanf("%lld%lld%lld",&Div,&L,&R);
scanf("%s",str);
int len=strlen(str);
for(RG i=0;i<len;++i){
cnt[str[i]-'0']=true;
maxnum=max(maxnum,(int)(str[i]-'0'));
}
printf("%lld\n",Solve(R)-Solve(L-1));
return 0;
}