codeforces (数位dp 记忆化搜索)

题意:枚举[a,b]之间的beautiful数(即这个数所有位上的数都能整除这个数)

题目链接:点击打开链接

思路: 用pos记录位置  num记录当前位置前面所有位组成的数 当时开不了这么大的数组  有知道1-9 的最小公倍数是2520 所以num记录 上文的num%2520 就可以了 现在就剩下记录当前位置前面各位都出现过0-9中的哪些数字了  我用s存前面出现过哪些数字 s的二进制各位分别对应各个数字

s二进制的位置: 9 8 7 6 5 4 3 2 1 0

0-9: 9 8 7 6 5 4 3 2 1 0       

这样s的二进制数就能把num各位是否出现过记录下来

eg:

num=14673111163

9 8 7 6 5 4 3 2 1 0

s(二进制): 0 0 1 1 0 1 1 0 1 0           num各位出现了1,3,4,6,7, 则s的二进制中第1,3,4,6,7,位标记为1 表示出现过其他位标记为0 表示没出现过

但是 这样还是会超内存  幸好 0 不能做分母 1 能整除任何数  所以s中不用标记0,1 是否出现过  那么s 的二进制可以变为8位分别对应

s二进制的位置: 7 6 5 4 3 2 1 0

0-9: 9 8 7 6 5 4 3 2

这样就不会超内存了

见别人也有用离散化的 

但是还是喜欢自己的状态压缩存

//数位dp 记忆化搜索
//
#include<stdio.h>
#include<string.h>

__int64 dp[30][3000][256];//dp[pos][num][s] 存位置为pos 前面l-pos位数对2520取余后的值  前面各位上出现的数字的状态
int digit[30];//存各位数字


int ok(int num,int s)//判断当前枚举的数字的各位数字 是否能整除num(当前枚举的数对2520取余后的余数)
{
    int i;
    for(i=0;(1<<i)<=s;i++)//扫描s二进制的各位
    {
        if(((1<<i)&s)!=0)//判断s中二进制第i位是否是1 如果是1说明当前枚举的结果某位上是(i+2)
            if(num%(i+2)!=0)//如果枚举的结果不能被(i+2)整除 则这个结果不是beautiful数
            return 0;
    }
    return 1;
}

__int64 dfs(int pos,int num,int s,int doing)//pos 枚举位置 num前面位组成的数对2520取余后的结果 s前面各位出现了(2~9中的那些) doing是否为上界
{
    if(pos==-1)
    {
        //if(s==0&&num==0)return 0;不能用这个条件去掉多枚举的0000,因为s=0表示当前枚举的数只出现过0,1 num=0 说明枚举的数位0 或者能被2520整除 当枚举的数为(只有1,0组成)11...0.01.%2520=0时这个数是beautiful数 当时会被返回0
        if(s==0)return 1;



        if(ok(num,s)==1)
            return 1;
            return 0;
    }

    if(!doing && dp[pos][num][s]!=-1)return dp[pos][num][s];

    int End,i;
    __int64 ans=0;
    End=doing ?digit[pos]:9;
    for(i=0;i<=End;i++)
    {
     if(i==0||i==1)//因为所有数都能被1整除 0 不能做分母 所以s中不存0,1
            ans+=dfs(pos-1,(num*10+i)%2520,s,doing && i==End);
     else
     {

         ans+=dfs(pos-1,(num*10+i)%2520,(s|(1<<(i-2))),doing && i==End);//s|(1<<(i-2)) 把当前位置枚举的数存到s中
     }
    }
    if(!doing )
        dp[pos][num][s]=ans;
    return ans;
}
__int64 solve(__int64 x)
{
    if(x<=0)return 0;
    int l=0;
    memset(digit,0,sizeof(digit));


    while(x>0)
    {
        digit[l++]=x%10;
        x=x/10;
    }
    return dfs(l-1,0,0,1)-1;// 减一是为了减去全为0的情况
}
int main()
{
    int t;
    __int64 a,b;
    scanf("%d",&t);
    memset(dp,-1,sizeof(dp));
    while(t--)
    {
        scanf("%I64d %I64d",&a,&b);
        printf("%I64d\n",solve(b)-solve(a-1));
    }
    return 0;
}



  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值