zoukankan      html  css  js  c++  java
  • bzoj1799(洛谷4127)同类分布(月之谜)

    题目:https://www.lydsy.com/JudgeOnline/problem.php?id=1799

       https://www.luogu.org/problemnew/show/P4127

    经典dp!

    一个数能被它的各位和整除,在L-R内有多少个。

    1.数位dp的套路,先预处理出第i位、后面任意的所有情况。

      因为涉及整除,所以状态有“模当前数余几”;

      因为涉及各位和,所以状态有“i位和为j”和“模k”;

    好了我们有了一份会MLE的四维代码,而且有会超时的18位预处理,答案好歹是正确的。

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    using namespace std;
    typedef long long ll;
    const int sx=18*9;
    ll tmp,f[19][165][165][165],cnt1,cnt2;
    int lmm,a[19];
    ll pw(int k)
    {
        ll mul=1;
        for(int i=1;i<=k;i++)
            mul*=10;
        return mul;
    }
    void pre()
    {
        for(int k=1;k<=sx;k++)
                f[0][0][k][0]=1;
        int lm=0;
        for(int i=1;i<=18;i++)
        {
            lm=i*9;
            for(int j=0;j<=lm;j++)
                for(int k=1;k<=sx;k++)
                    for(int l=0;l<k;l++)
                        for(int p=0;p<=9&&j-p>=0;p++)
                            f[i][j][k][l]+=f[i-1][j-p][k][((l-p*pw(i-1))%k+k)%k]; 
    //                        if(i==1&&j==p&&k==p)printf("j=%d k=%d l=%d p=%d ff=%lld
    ",j,k,l,p,f[i][j][k][l]);
                            
        }
    }
    void chl()
    {
        lmm=0;
        while(tmp)
        {
            a[++lmm]=tmp%10;
            tmp/=10;
        }
    }
    ll calc()
    {
        int t=0;
        ll cnt=0,lj=0;
        for(int i=lmm;i;i--)
        {
            int ll=a[i];if(i==1)ll++;
            for(int p=0;p<ll;p++)
                for(int s=max(1,t+p);s<=sx;s++)
                    cnt+=f[i-1][s-t-p][s][(s-(lj+p*pw(i-1))%s)%s]; 
            t+=a[i];lj+=a[i]*pw(i-1);
        }
        return cnt;
    }
    int main()
    {
        scanf("%lld",&tmp);tmp--;
        pre();
        chl();
        cnt1=calc();
    //    printf("(%lld)
    ",cnt1);
        scanf("%lld",&tmp);
        chl();
        cnt2=calc();
    //    printf("(%lld)
    ",cnt2);
        printf("%lld",cnt2-cnt1);
        return 0;
    }
    MLE+TLE

    在bzoj 50s 下应该还不会超时,考虑解决MLE。

    2.发现pre转移的时候,模数s是固定的,即每个状态由模数相同的状态转移过来。

      所以只要把模数s的枚举放在最外面,就可以省掉这一维,代价是每一个s都要预处理一遍。

      尽可能优化一点。

        1)发现L和R的预处理有一部分是重复的,所以把R - L这一部分也在循环里实现而不是最后再减;

        2)(微弱)其实预处理到lm[1]-1位就行了。

    bzoj上能过了(30s+),洛谷上超时一个点,而且其他点好慢……

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    using namespace std;
    typedef long long ll;
    ll f[22][180][180],l,r,mul[22],ans;
    int lm[3],a[3][22],sx;
    void pw()
    {
        mul[0]=1;
        for(int i=1;i<=lm[1];i++)
            mul[i]=mul[i-1]*10;
    }
    void pre(int s)
    {
        memset(f,0,sizeof f);
        f[0][0][0]=1;//
        int lmm=0;
        for(int i=1;i<=lm[1];i++)
        {
            lmm=min(s,i*9);
            for(int j=0;j<=lmm;j++)
                for(int l=0;l<s;l++)//l<j是错的(为何) 
                    for(int p=0;p<=9&&j-p>=0;p++)
                        f[i][j][l]+=f[i-1][j-p][((l-p*mul[i-1])%s+s)%s];//考虑l-p*pw(i-1)<0 
        }
    }
    void chl(int k,ll tmp)
    {
        while(tmp)
        {
            a[k][++lm[k]]=tmp%10;
            tmp/=10;
        }
    }
    ll calc(int k,int s)
    {
        ll cnt=0;
    //    printf("s=%d
    ",s);
        int t=0;ll lj=0;
        for(int i=lm[k];i;i--)
        {
            int llm=a[k][i];if(i==1)llm++;
            for(int p=0;p<llm&&s-t-p>=0;p++)
                    cnt+=f[i-1][s-t-p][(s-(lj+p*mul[i-1])%s)%s];//考虑lj+p*pw(i-1)==0 
            t+=a[k][i];lj+=a[k][i]*mul[i-1];
    //        printf("i=%d cnt=%d
    ",i,cnt);
        }
        return cnt;
    }
    int main()
    {
        scanf("%lld%lld",&l,&r);
        chl(0,l-1);chl(1,r);
        sx=(lm[1]-1)*9+a[1][lm[1]];pw();
        for(int s=1;s<=sx;s++)
        {
            pre(s);
            ans+=calc(1,s)-calc(0,s);
        }
        printf("%lld",ans);
        return 0;
    }
    TLE

    3.发现大家写的都是记忆化深搜。

      为什么比较快呢?因为它不需要把一些用不到的状态也预处理出来。

      但具体是什么我也不清楚。详见代码?

      然后发现大牛的代码:写得好好!于是近乎抄了下来。

      这里只要有最后那个d=0时的限制就不用记录各位和为j了,只要最后整个各位和等于s就行。

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    using namespace std;
    typedef long long ll;
    ll input()
    {
        int f=1;ll s=0;char ch=getchar();
        while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
        while(ch>='0'&&ch<='9'){s=s*10+ch-'0';ch=getchar();}
        return s*f;
    }
    const int N=165,M=20;
    ll x,y,dp[2][M][N][N];
    int cnt,a[M],dfn[2][M][N][N],tot,mod;
    ll calc(bool p,int d,int s,int m)//p为有无限制 ,d是第几位,s是模数也是各位和 ,m是前面填好的东西%s余数 
    {
        if(!d)return (!s&&!m);//初值,第0位时仅当s为0(个位填好后整个的各位和为s)、m为0(整除)时为1
        if(dfn[p][d][s][m]==tot)return dp[p][d][s][m];
        dfn[p][d][s][m]=tot;ll tmp=0;
        int l=max(0,s-9*(d-1)),r=min(((p)?9:a[d]),s);//r<=s
        for(int i=l;i<=r;i++)tmp+=calc(p|(i<a[d]),d-1,s-i,(m*10+i)%mod);//i是当前位填几 
        return dp[p][d][s][m]=tmp;
    }
    ll solve(ll ret)
    {
        for(cnt=0;ret;ret/=10)a[++cnt]=ret%10;
        ll ans=0;int sx=cnt*9;
        for(mod=1;mod<=sx;mod++)tot++,ans+=calc(0,cnt,mod,0);
        return ans;
    }
    int main()
    {
        x=input();y=input();
        return printf("%lld",solve(y)-solve(x-1)),0;
    }

    有点奇怪的是下面这份代码数组范围应该都够,但仅改数组范围就能修复一个RE(变成上边那个TLE),也就是说数组越界?

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    using namespace std;
    typedef long long ll;
    ll f[19][165][165],l,r,mul[19],ans;
    int lm[2],a[2][19],sx;
    void pw()
    {
        mul[0]=1;
        for(int i=1;i<=lm[1];i++)
            mul[i]=mul[i-1]*10;
    }
    void pre(int s)
    {
        memset(f,0,sizeof f);
        f[0][0][0]=1;//
        int lmm=0;
        for(int i=1;i<=lm[1];i++)
        {
            lmm=min(s,i*9);
            for(int j=0;j<=lmm;j++)
                for(int l=0;l<s;l++)//l<j是错的(为何) 
                    for(int p=0;p<=9&&j-p>=0;p++)
                        f[i][j][l]+=f[i-1][j-p][((l-p*mul[i-1])%s+s)%s];//考虑l-p*pw(i-1)<0 
        }
    }
    void chl(int k,ll tmp)
    {
        while(tmp)
        {
            a[k][++lm[k]]=tmp%10;
            tmp/=10;
        }
    }
    ll calc(int k,int s)
    {
        ll cnt=0;
    //    printf("s=%d
    ",s);
        int t=0;ll lj=0;
        for(int i=lm[k];i;i--)
        {
            int llm=a[k][i];if(i==1)llm++;
            for(int p=0;p<llm&&s-t-p>=0;p++)
                    cnt+=f[i-1][s-t-p][(s-(lj+p*mul[i-1])%s)%s];//考虑lj+p*pw(i-1)==0 
            t+=a[k][i];lj+=a[k][i]*mul[i-1];
    //        printf("i=%d cnt=%d
    ",i,cnt);
        }
        return cnt;
    }
    int main()
    {
        scanf("%lld%lld",&l,&r);
        chl(0,l-1);chl(1,r);
        sx=(lm[1]-1)*9+a[1][lm[1]];pw();
        for(int s=1;s<=sx;s++)
        {
            pre(s);
            ans+=calc(1,s)-calc(0,s);
        }
        printf("%lld",ans);
        return 0;
    }
    一个RE

    真是经典。以后也许要回顾回顾了呢。

    人家的代码好好……

  • 相关阅读:
    您上次已将进行的搜狗输入法的安装或卸载,操作要求重启。您在重启之后才能继续新的输入法安装卸载程序。
    CUDPP主页
    cudaMemcpy2D介绍
    cudaMallocPitch – 向GPU分配存储器
    leanote开源云笔记
    OpenMP常用函数
    PRmakefile文件
    安装gcc,g++
    Getting aCC Error :name followed by "::" must be a class or namespace name"
    acc_set_device_num && acc_get_device_num例程
  • 原文地址:https://www.cnblogs.com/Narh/p/8759348.html
Copyright © 2011-2022 走看看