题意:枚举[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;
}