CF55D Beautiful numbers

题目链接http://codeforces.com/problemset/problem/55/D

分析

看到这道题的时候我想到的是状压DP,压一下0~9是否出现过,但发现之后就不好弄了,因为它不仅可能出现,而且还会出现很多次,看数据范围就知道暴力肯定不可能的,这数据大小就算跑一遍循环也能T掉,所以这里就要用到数位DP了。

状压DP是用一个十进制数来表示二进制的状态,本质上还是暴力,不过是优化了。常用到位运算

数位DP跟它也差不多,算是一种暴力,主要用来解决数位上计数的问题,比如本题。主要是通过记忆化搜索来实现

因为查询时查询一个区间很不方便,所以将他拆成两个区间相减的形式,这个地方应该不难看出来。

接下来就是数位DP的内容了,先考虑最原始的版本,因为我要一位一位的去考虑,所以位数占了一维,又因为要判断是否是这什么什么\(Numbers\),所以位数所表示的十进制数也要存下来,那么怎么判断是不是这种数字呢?最坏的情况是含了0到9的所有数字,既然每个数字都能整除,那么必定可以整除它们的最小公倍数,所以公倍数也要占一维,于是定义\(dp_{i,j,k}\)为还剩下\(i\)位,并且前\(i\)位表示的数为\(j\),这\(i\)位数字的最小公倍数为\(k\)时的
答案是多少,这里解释一下\(i\)这一维,比如123456,前三位分别是1,2,3,表示的数即为123,然后就能通过枚举得到\(dp_{i,j,k}\)=\(\sum_{i=0}^{Max}\)\(dp_{i-1,j*10+i,lcm(k,i)}\),lcm表示的就是最小公倍数,这个应该也能看懂,他其实就是暴力的枚举了每
一位上的数字。

这个数组开了之后肯定会MLE,考虑一下优化,1~9的最小公倍数为\(5*7*8*9==2520\),也就是说第三维的状态不可能比2520还要大,但开2520还是会MLE,继续思考,1到2520很多数字我们都是用不到的,我们用到的实际上只有2520的因数,所以可以提前预处理出来,然后把第三维的状态改成最小公倍数对应的数字为\(k\),相当于一个离散化。

最棘手的东西还是中间这一维,直接记录的话会发现它最大\(9*10^{18}\)次方,那么有没有什么好的办法来压一下这个大小呢?先思考一下,我们最后判断的是什么,就是这个数\(mod\)它每个位数字上的最小公倍数是否==0,设这个数为\(x\),最小公倍数为\(lcm\),因为涉及到\(mod\),所以从同余的角度来考虑。

引理:如果\(a\)\(b\)的因子,那么\(x\)同余\(x\)%\(b(mod\) \(a)\)

证明:因为\(a\)\(b\)的因子,所以可以设\(b=na,n\)是正整数
不妨设\(x=qb+r,r=0,1,2……b-1\)
所以\(x\)\(\equiv\)\(x-qna\) \((mod\) \(a)\)
\(x\)\(\equiv\)\(x-qb\) \((mod\) \(a)\)
\(x-qb\)即为\(r\)又因为\(r\)\(x\)%\(b\) \((mod\) \(a)\)
所以\(x\)\(\equiv\)\(x\)%\(b\) \((mod\) \(a)\)

有了这个引理,就好办多了,所有的lcm都一定是2520的因子,所以\(x\)\(\equiv\)\(x\)%\(2520\) \((mod\) \(lcm)\)
也就是说,\(x\)\(x\)%\(2520\)实际上是等价的,那为什么我们不把它%一下,于是第二维也就只需要开2520,空间问题就解决了,加上之前的一些分析,这个问题基本解决。

细节

1.记忆化
反正都搜过了,不记白不记。但是记录的时候要注意,因为枚举每个位置的时候,不能超过这个数原本的值,超过了就会多记答案,所以当当前位是最大值的时候,不能记忆,同理返回值时也是。比如113,枚举百位时显然不能枚举到2,但当百位是0时,十位却能够到9,所以判断最大值时要保证每一位都是最大值。
2.longlong
这题不开longlong等着WA吧。
3.最大公约数和最小公倍数
最大公约数我用的辗转相除法,原理挺简单,就一直除,直到可以除尽为止。
最小公倍数就是两数的乘积除以最大公约数,这个就根据最小公倍数的定义自行理解吧

#include<iostream>
#define ll long long
using namespace std;
const int N=2525;
ll dp[20][N][50];
int p[N],hh,num[20],top;
int gcd(int a,int b){
    return a%b?gcd(b,a%b):b;
}
int Lcm(int a,int b){
    return b==0?a:a/gcd(a,b)*b;
}
ll dfs(int x,int sum,int lcm,bool ismx){
    if(x==0)return sum%lcm==0;
    if(!ismx&&dp[x][sum][p[lcm]])return dp[x][sum][p[lcm]];
    int Max=ismx==1?num[x]:9;
    ll ans=0;
    for(int i=0;i<=Max;i++)
        ans+=dfs(x-1,(sum*10+i)%2520,Lcm(lcm,i),ismx&&i==Max);
    if(!ismx)dp[x][sum][p[lcm]]=ans;
    return ans;
}
ll calc(ll x){
    top=0;
    while(x){
        num[++top]=x%10ll;
        x/=10ll;
    }
    return dfs(top,0,1,1);
}
int main(){
    for(int i=1;i<=2520;i++)
        if(2520%i==0)p[i]=++hh;
    int t;
    cin>>t;
    while(t--){
        ll l,r;
        cin>>l>>r;
        cout<<calc(r)-calc(l-1)<<'\n';
    }
}
posted @ 2020-04-16 23:52  An_Fly  阅读(293)  评论(0编辑  收藏  举报