[数位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;
}
posted @ 2020-09-19 15:35  AE酱  阅读(110)  评论(0编辑  收藏  举报