bzoj 1799: [Ahoi2009]self 同类分布

数位DP|记忆化搜索

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

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

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

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

最后就是判边界的参数。

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

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

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

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

因为每个位上最大是9,所以我们从1枚举到\(len \times× 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 \rightarrow 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;
}
全部评论

相关推荐

不愿透露姓名的神秘牛友
11-21 17:16
科大讯飞 算法工程师 28.0k*14.0, 百分之三十是绩效,惯例只发0.9
点赞 评论 收藏
分享
10-09 22:05
666 C++
找到工作就狠狠玩CSGO:报联合国演讲,报电子烟设计与制造
点赞 评论 收藏
分享
点赞 收藏 评论
分享
牛客网
牛客企业服务