zoukankan      html  css  js  c++  java
  • 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)<<'
    ';
        }
    }
    
  • 相关阅读:
    十日冲刺第一次会议任务领取详解
    Android studio新建class文件报错
    代码整洁之道阅读笔记03
    本周学习进度条6
    echarts基本用法
    梦断代码阅读笔记01
    软件工程小组任务
    本周学习进度条5
    eclipse界面设置和常用技巧
    团队项目——TD课程通
  • 原文地址:https://www.cnblogs.com/anyixing-fly/p/12716901.html
Copyright © 2011-2022 走看看