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;
}

原文地址:https://www.cnblogs.com/wljss/p/11496603.html

时间: 2024-11-08 16:39:08

bzoj 1799: [Ahoi2009]self 同类分布的相关文章

bzoj 1799: [Ahoi2009]self 同类分布 题解

[原题] 1799: [Ahoi2009]self 同类分布 Time Limit: 50 Sec  Memory Limit: 64 MB Submit: 554  Solved: 194 [Submit][Status] Description 给出a,b,求出[a,b]中各位数字之和能整除原数的数的个数. Input Output Sample Input 10 19 Sample Output 3 HINT [约束条件]1 ≤ a ≤ b ≤ 10^18 Source Day1 [分析]

BZOJ 1799: [Ahoi2009]self 同类分布 ( 数位dp )

传送门   也不是很难,微坑的一点是dp数组在扫描每一次数位和的时候都要初始化, 因为没有存 用来mod的总数位和x 的位置... ... 然后这个dp的式子也稍微想了一下...还是练题太少... 1 #include<cmath> 2 #include<cstdio> 3 #include<cstring> 4 #include<iostream> 5 #include<algorithm> 6 #include<queue> 7

【BZOJ】1799: [Ahoi2009]self 同类分布

[题意]给出a,b,求出[a,b]中各位数字之和能整除原数的数的个数.1 ≤ a ≤ b ≤ 10^18 [算法]数位DP [题解] 感觉这种方法很暴力啊. 枚举数位和1~162(不能枚举0,不然会模0,相当于除0),记忆化f[pos][sum][val],sum表示当前数位和,val表示数字取模枚举的数位和. 每次sum+i和(val*10+i)%MOD转移. sum用减法优化,即记忆化(MOD-sum),但是枚举过程中都要memset,导致效率低下,记忆化效果很差. 要什么方法才能跑1.3s

bzoj 1799: [Ahoi2009]self 类似的分布 解读

[原标题] 1799: [Ahoi2009]self 同类分布 Time Limit: 50 Sec  Memory Limit: 64 MB Submit: 554  Solved: 194 [id=1799" style="color:blue; text-decoration:none">Submit][id=1799" style="color:blue; text-decoration:none">Status] Descr

bzoj1799 [Ahoi2009]self 同类分布

传送门:http://www.lydsy.com/JudgeOnline/problem.php?id=1799 [题解] 一眼看过去,数位dp! 想了想,发现数字和最多也就是$m = 9 \times 18 = 162$种,好像不是很大. 考虑枚举每种数字和$p$,做一遍dp. 设$f_{i,j,k}$表示到第$i$位,当前真实数字模$p$余$j$,当前所有数字的和为$k$的方案数.(不考虑前导0问题) 这个可以通过一个$O(18\times p^2 \times 10)$的动态规划解决. 接

手打AC的第2道数位DP:BZOJ1799: [Ahoi2009]self 同类分布

先讲下个人对于数位DP的看法吧... 挺难理解的 首先需要明白的一点:前缀和很重要 其次:必须用到记忆化搜索(本人蒟蒻,必须要用这种方法降低难度) 然后呢,需要判断约束的条件:(1.前缀0(有时需要,有时不需要):2.数位的取值(基本都需要)) 比如这道题,乍一看无思路,然后呢.... 就会出现很神奇的事情 大胆尝试(第一次)3维(题解本来是4维的...) 居然就XJB A了... 略显蛋疼... 回归正题*2: 本题的状态有些难找,但是由于数位最多的也只有pos,所以就可枚举所有的数位和...

HYSBZ - 1799 self 同类分布

self 同类分布 HYSBZ - 1799 给出a,b,求出[a,b]中各位数字之和能整除原数的数的个数.Sample Input 10 19 Sample Output 3 Hint [约束条件]1 ≤ a ≤ b ≤ 10^18 约束:一个数是它自己数位和的倍数,直接dp根本找不到状态,枚举数位和,因为总就162,然后问题就变成了一个数%mod=0,mod是枚举的,想想状态:dp[pos][sum][val],当前pos位上数位和是sum,val就是在算这个数%mod,(从高位算  *10

[AHOI2009]同类分布

[AHOI2009]同类分布 求区间\([l,r]\)内的数,满足自己能整除自己各个数位上的和的数的个数,\(l,r\leq 10^{18}\). 解 不难得知设\(f_n\)为n以内的满足条件的数,答案即\(f_r-f_l\),因为递推中要表现整除,可以考虑摸递推. 要表现各个数位的和,又要变现长度,而且还要表现摸数,还有余数,故设\(f[i][j][k][l]\)表示i位的数,各数位的和为j,摸数为k,余数为l的数的方案数,因此不难有 \[f[i][j][k][l]=\sum_{p=0}^9

BZOJ 1800: [Ahoi2009]fly 飞行棋

二次联通门 : BZOJ 1800: [Ahoi2009]fly 飞行棋 /* BZOJ 1800: [Ahoi2009]fly 飞行棋 乱搞一下就好 */ #include <cstdio> #include <iostream> #define rg register inline void read (int &n) { rg int c = getchar (); for (n = 0; !isdigit (c); c = getchar ()); for (; i