Codeforces 55D Beautiful Number
题意:n组测试数据,每组给你一个区间要求出在这区间内有多少个数字能被它的各个位上的数字整除(0除外)。
思路:dp[i][j][k],i代表当前是第几位,j对2520取余后的数,k代表当前所有位数的lcm,空间复杂度为18*2520*2520会炸,但是1~9的最小公倍数最多只有48个是2520的所有因子,可以hash一下优化空间,复杂度就是18*2520*48就不会炸了。
因为所有的数字都是由0~9组成的,lcm(1~9)=2520,所以只要能整除2520就肯定满足条件。
用记忆化搜索的方法遍历所有情况。举个例子方便理解。
比如数字325648;
从高位开始,3这一位的取值上限为3当这一位是0~2是下一位可以取任意值0~9,当这一位是3,下一位就按照2为上限来取。当跑完所有位,答案也就出来了。求得是0到某个数字满足条件的个数,区间就是右边的答案减去左边-1的答案就好了(闭区间);
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#define LL long long
using namespace std;
const int Mod=2520;
LL dp[20][3000][50];
int x[3000],temp[100];
void init()
{
int cont=0;
memset(dp,-1,sizeof(dp));
for(int i=1;i<=Mod;i++)
if(Mod%i==0)
x[i]=cont++;
}
LL LCM(LL a,LL b)
{
return a*b/__gcd(a,b);
}
LL dfs(int pos,int num,int lcm,int flag)//处理的当前位 前面的数对2520的取余结果 前面各个位上数字的最小公倍数 是否可以曲任意值
{
if(pos==-1) return num%lcm==0;//找完了,检查自身是否满足条件
if(!flag&&(dp[pos][num][x[lcm]]!=-1)) return dp[pos][num][x[lcm]];//找过了这个状态直接返回
int stop=flag?temp[pos]:9;//判断这一位的取值上限是多少
LL ans=0;
for(int i=0;i<=stop;i++)
ans+=dfs(pos-1,(num*10+i)%Mod,i?LCM(i,lcm):lcm,(i==stop)&&flag);//判断下一位能不能取任意值(这一位可以取是任意值,则下一位必定可以,如果这一位不能取任意值则,当这一位的取值不是上限是下一位可取任意值)
if(!flag) dp[pos][num][x[lcm]]=ans;//这一位可取任意值代表长度是pos的已经全部得到答案所以存起来
return ans;
}
LL solve(LL n)
{
int cont=0;
while(n)
{
temp[cont++]=n%10;
n/=10;
}
return dfs(cont-1,0,1,1);//刚开始不能取任意值,任何数都能整除1
}
int main()
{
init();
int ncase;
scanf("%d",&ncase);
while(ncase--)
{
LL n,m;
scanf("%lld%lld",&n,&m);
printf("%lld\n",solve(m)-solve(n-1));
}
}