zoukankan      html  css  js  c++  java
  • bzoj 1799: [Ahoi2009]self 同类分布

    数位DP|记忆化搜索

    听隔壁巨佬说DP和记搜可以互相转换

    显然这个题是可以用记忆化搜索过的,那我们应传哪几个参数?

    首先就是记搜最基本的位置标记。

    然后就是枚举的数字各位之和,以及取模之后的余数(判断能否整除某个数)。

    最后就是判边界的参数。

    当我们搜到最后一位时如果余数为0,并且各位之和=mod,那就直接返回1,否则返回零。

    显然我们把这几个数装进数组里,是开不下的。

    long long 为8字节 (8 imes 20 imes 200 imes 200 imes 200 div1024 div1024 =1220.703125 MB8×20×200×200×200÷1024÷1024=1220.703125MB)

    所以我们考虑枚举模数(各位之和)mod,就要求n数字各位之和=mod,n%mod=0,。

    因为每个位上最大是9,所以我们从1枚举到(len imes× 9).这样我们就将所有情况(数字各位之和)都考虑了进去。

    这样我们就省掉了DP数组中模数那一维,妥妥的能开下。(〃'▽'〃)

    我们将相同的状态用数组记录,这样在下一次搜到该状态时就能直接调用啦~~

    然后分享几个卡常技巧。

    今天做了一道和这个题类似的毒瘤卡常题,代码十几分钟,卡常卡了两小时QAQ,莫非真的是人丑常数大。

    首先,我们可以剪枝,若sum(数字各位之和>mod),直接return 0,因为我们越往后,sum只会越来越大,不可能等于mod。

    然后就是清空DP数组时将memset改为for循环,我们只用将我们上一次用到的数组清空(1~枚举的mod)就可以了(实测跑的飞快)

    再然后就是搜索,正常情况下我们是这样写的:

    cin>>a>>b;cout<<calc(b)-calc(a-1);
    

    但是我们忽视了一个问题,那就是相同的mod我们应该只记搜一次,但因为我们分开来计算,所以就算了两次,常数巨大>_<

    那么我们就在计算b的答案的同时顺便减去a的答案就行了,这样我们每个mod只用到了一遍。

    最后就是一些基本的卡常操作啦~~

    卡常结果:$ 7.58s ightarrow 3.11s$

    还有如果让求C进制下的答案,我们只需要将代码中的10改为C就可以了

    最后献上我丑陋的代码

    #include<cstdio>
    #include<iostream>
    using namespace std;
    bool ok;
    int len,cnt,w[21],ww[71],mod;
    long long b,a,dp[21][205][205];
    inline long long read()
    {
        char ch;long long x=0,f=1;
        while(!isdigit(ch=getchar()))
        {
            (ch=='-')&&(f=-f);
        }
        while(isdigit(ch))
        {
            x=(x<<1)+(x<<3)+(ch^48);
            ch=getchar();
        }
        return x*f;
    } 
    inline void write(long long x)
    {
         if(x<0) putchar('-'),x=-x;
         if(x>9) write(x/10);
         putchar(x%10+'0');
    }
    long long dfs(int l,int sum,int m,int g)
    {
        if(sum>mod)return 0; 
        if(l==0)
            return (m==0&&sum==mod);
        register int end=g?w[l]:9;
        if(!g&&dp[l][sum][m]!=-1)return dp[l][sum][m];
        register long long ans=0;
        for(register int i=0;i<=end;++i)
            ans+=dfs(l-1,sum+i,(m*10+i)%mod,g&&(i==end));
        if(!g)dp[l][sum][m]=ans;
        return ans;
    }
    long long dfs1(int l,int sum,int m,int g)
    {
        if(sum>mod)return 0; 
        if(l==0)
            return (m==0&&sum==mod);
        register int end=g?ww[l]:9;
        if(!g&&dp[l][sum][m]!=-1)return dp[l][sum][m];
        register long long ans=0;
        for(register int i=0;i<=end;++i)
            ans+=dfs1(l-1,sum+i,(m*10+i)%mod,g&&(i==end));
        if(!g)dp[l][sum][m]=ans;
        return ans;
    }
    inline long long c1(long long x)
    {
        if(!ok)
        {
            cnt=0;
            while(x)
            {
                ww[++cnt]=x%10;
                x/=10;
            }
            ok=1;
        }
        return dfs1(cnt,0,0,1);
    }
    inline long long c(long long x)
    {
        int i,j,k;
        len=0;
        while(x)
        {
            w[++len]=x%10;
            x/=10;
        }
        register long long ans=0;
        for(mod=1;mod<=len*9;++mod)
        {
            for(i=0;i<=len;++i)
            for(j=0;j<=mod;++j)
            for(k=0;k<=mod;++k)
            dp[i][j][k]=-1;
            ans+=dfs(len,0,0,1);
            ans-=c1(a-1);
        }
        return ans;
    }
    int main()
    {
        a=read(),b=read();
        write(c(b));
        return 0;
    }
    
  • 相关阅读:
    Centos7下thinkphp5.0环境配置
    win10蓝牙鼠标无法连接,需pin码
    thinkphp5自带workerman应用
    php文件加密(screw方式)
    centos修改ssh默认端口号的方法
    修改CentOS ll命令显示时间格式
    在线编辑器的原理简单示例
    [转载]提升SQLite数据插入效率低、速度慢的方法
    linux 客户机挂载vitualbox共享文件夹
    virtualbox linux客户机中安装增强功能包缺少kernel头文件问题解决
  • 原文地址:https://www.cnblogs.com/wljss/p/11496603.html
Copyright © 2011-2022 走看看