您的位置:首页 > 编程语言 > C语言/C++

CF55D Beautiful numbers (数位DP)

2016-07-18 16:56 375 查看

codeforces 55D Beautiful numbers (数位DP)

原题地址:http://codeforces.com/problemset/problem/55/D

D. Beautiful numbers

time limit per test
4 seconds

memory limit per test
256 megabytes

input
standard input

output
standard output

Volodya is an odd boy and his taste is strange as well. It seems to him that a positive integer number is
beautiful if and only if it is divisible by each of its nonzero digits. We will not argue with this and just count the quantity of beautiful numbers in given ranges.

Input
The first line of the input contains the number of cases
t (1 ≤ t ≤ 10). Each of the next
t lines contains two natural numbers
li and
ri (1 ≤ li ≤ ri ≤ 9 ·1018).

Please, do not use %lld specificator to read or write 64-bit integers in C++. It is preffered to use
cin (also you may use
%I64d).

Output
Output should contain t numbers — answers to the queries, one number per line — quantities of beautiful numbers in given intervals (from
li to
ri, inclusively).

Examples

Input
1
1 9


Output
9


Input
1
12 15


Output
2


题目的大意,就是要求出在区间 [ l,r ] 内,能被自身每一位数整除的数有多少个。

十分明显就是数位DP。

数位DP可以用记忆化搜索解决。

要点有两个,一个是DP数组的设计,一个是DFS的状态设计。

考虑到,要被自身每一位整除,那么,等价于能被每一位数的最小公倍数整除

而通过计算得知,1~9 的最小公倍数是2520 。

DP数组的设计,一般我们会想到DP [ 位数 ] [ 最小公倍数 ] [ mod 2520的余数 ]。

但是这样一来,我们就要将数组开到DP [ 20 ] [ 2520 ] [ 2520 ]。这样必然爆内存。

而考虑到,1~9的不同组合的最小公倍数也只有48个,我们可以将DP的第二维降到48。

DFS的状态可以设计为记录之前每一位的最小公倍数,和对2520的余数送给下一层。

因此得出DP [ 20 ] [ 48 ] [ 2520 ] ,分别代表数位,最小公倍数,对2520的余数。

long long int dfs( int pos , int lcm , int mod , bool limit),分别处理数位,最小公倍数,对2520的余数,界限。

那么我们就可以开工了~

穹妹镇贴~↓



数位DP有几个需要注意的地方,我因为这些细节吃了不少苦头(一整天都在找BUG):

在求余的时候,一般会这么求,比如

1234%18

因为要将数位分开处理,传递给下一位

所以我一开始用的是:

(((1*10^3 % 18 + 2*10^2) % 18 + 3*10^1) %18 + 4*10^0)%18

来求得最终的余数

然而这样免不了用pow函数。而pow函数的误差实在太大,导致在CF上,数据达到1000时,就出现了误差,第四组样例就已经挂了……

找了一晚上的BUG,检查各种细节。最后在翌日的早上,想到了pow的误差。

于是手写了一个pow函数,我想应该能好些,果然,第四组样例过了,挂在了第五组……

感觉整个人都不好了 T^T

手写的pow也有误差,那就只能不用了。

看了看大神的代码。大神没有用pow。

他是这么求余数的↓

1234%18

=(((1%18)*10 + 2)%18*10 + 3)%18 *10 + 4)%18

这样的话,每一次只需要乘以10。不需要用pow或者进行太多的累乘。

下面贴代码:#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<algorithm>
using namespace std;
int lcms[2523];
long long int dp[20][48][2524];
int digit[20];
int gcd(int a,int b)
{
if(b==0)return a;
else return gcd(b,a%b);
}

void init()
{
int num=0;
for(int i=1; i<=2520; i++)
if(2520%i==0)
{
lcms[i]=num++;
}
}
long long int dfs(int pos,int lcm,int mod,bool limit)
{
if(pos==-1)
{
return mod%lcm==0;
}
if(!limit&&dp[pos][lcms[lcm]][mod]!=-1)
{
return dp[pos][lcms[lcm]][mod];
}
long long int res=0;
int tmp=limit? digit[pos]:9;

for(int i=0; i<=tmp; i++)
{
int Nmod=(mod*10+i)%2520;
int Nlcm;
if(i==0||i==1)
{
Nlcm=lcm;
}
else
{
Nlcm=lcm/gcd(lcm,i)*i;
}
res+=dfs(pos-1,Nlcm,Nmod,limit&&i==tmp);
}
if(!limit)
{
dp[pos][lcms[lcm]][mod]=res;
}
return res;
}
long long int cal(long long int a)
{
int len=0;
while(a)
{
digit[len++]=a%10;
a=a/10;
}
return dfs(len-1,1,0,1);
}
int main()
{
long long int t,a,b;
scanf("%I64d",&t);
memset(dp,-1,sizeof(dp));
memset(lcms,-1,sizeof(lcms));
init();
while(t--)
{
scanf("%I64d%I64d",&a,&b);
printf("%I64d\n",cal(b)-cal(a-1));
}
return 0;
}

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息