数位dp总结---LZM

数位DP总结 BY----LZM

当你学会了数位DP,那么你会发现,在考场上,你也写不出来。

首先给出一道例题:

给出一个区间 [l,r] ,求出区间内每个数字的数位和,答案 mod 9982443531l,r1018

样例输入           样例输出
24 69                411

考虑枚举从 lr ,对于每个数都拆分成数位,然后暴力的加。
复杂度很显然是 O(rlogr)爆chao,非常痛苦。

考虑优化,先想从 1l1 ,再想从 1r ,然后发现答案是一个前缀和的形式,两者相减即可得到答案。

所以我们只要有一个东西能够解决从 1x 的答案就可以轻松的求出来,这里给出方法。

假设从高位枚举到低位,对于每一个数位都可能有10个数字(0,1,2....8,9),为什么说可能呢,因为可能这个数字太大了,会超过 x ,的上界,比如在枚举 x=123时,我们已经枚举到了第二位,

我们枚举的---》 1

枚举的第一个数不能超过1,同样的,第二位不能超过二。

还有一种情况,我们枚举的---》 0

发现有前导零的情况,那么对于这一种我们就要记录下来。

那么怎么实现呢???

记忆化!搜!索!

我们在 dfs 中记录几个参数:

  • len:代表目前从高位到低位枚举的位数
  • lead:代表是否有前导零
  • top:代表是否到了上界
  • sum:代表此时的数位和

然后在开一个 4 维的 dp 记忆化数组。

为什么记忆化可以?!

因为 dp 数组记录的状态是唯一的,如果当前状态已经被搜过了,那么后面搜的一定是一样的。

以下给出代码。

#include<bits/stdc++.h>
#define int long long 

const int p=1e9+7;

int a[20];
int f[20][3][3][200];

int dfs(int len,int lead,int top,int sum)
{
    if(!len)return sum;//没有位数了
    if(!lead && !top && f[len][lead][top][sum]!=-1)return f[len][lead][top][sum];//状态被搜过了,直接回去
    int bound=top?a[len]:9,res=0;//如果上界到了就枚举到这一位最多能枚举到的
    for(int i=0;i<=bound;i++)res=(res+dfs(len-1,lead && !i,top && i==bound,sum+i))%p; 
    if(!lead && !top)f[len][lead][top][sum]=res;
    return res;
}

int read()//快读就是好
{
    int x=0,s=1;
    char ch=getchar();
    for(;!isdigit(ch);ch=getchar())if(ch=='-')s=-1;
    for(;isdigit(ch);ch=getchar())x=x*10+ch-48;
    return x*s;
}

signed main()
{
    memset(f,-1,sizeof(f));//记得清空
    int n=read()-1,ans=0,cnt=0;//初始n记得减1
    while(n)a[++cnt]=n%10,n/=10;//从高位到低位
    ans-=dfs(cnt,1,1,0);
    cnt=0;
    n=read();
    memset(f,-1,sizeof(f));
    while(n)a[++cnt]=n%10,n/=10;
    ans+=dfs(cnt,1,1,0);
    printf("%lld\n",(ans%p+p)%p);
    return 0;
}

接着下一道题

不含前导零且相邻两个数字之差至少为 2 的正整数被称为 windy 数。在 ab 之间,包括 ab ,总共有多少个 windy 数?

这个和上面的差不多,只是要换一下参数

记录前一个数,以及前导零和上界就可以和上一个题一样了,但是要记得一开始传参要从-2开始,因为一开始可以随便填。

#include<bits/stdc++.h>
#define int long long 

using namespace std;

const int N=30;

int n;
int a[N];
int ans;
int f[N][N][N][N];

void init(int x)
{
    memset(a,0,sizeof(a));
    n=0;
    while(x)a[++n]=x%10,x/=10;
}

int dfs(int len,int pos,int lead,int top)
{
    if(!len)return 1;
    if(!top && !lead && f[len][pos][lead][top]!=-1)return f[len][pos][lead][top];
    int bound=top?a[len]:9,res=0;
    for(int i=0;i<=bound;i++)
    {
        if(abs(pos-i)<2)continue;
        if(lead && !i)res+=dfs(len-1,-2,1,top && i==bound);
        else res+=dfs(len-1,i,0,top && i==bound);
    }
    if(!lead && !top)f[len][pos][lead][top]=res;
    return res;
}

int read()
{
    int x=0,s=1;
    char ch=getchar();
    for(;!isdigit(ch);ch=getchar())if(ch=='-')s=-1;
    for(;isdigit(ch);ch=getchar())x=x*10+ch-48;
    return x*s;
}

signed main()
{
    memset(f,-1,sizeof(f));
    int x=read();
    x--;
    init(x);
    ans-=dfs(n,-2,1,1);
    x=read();
    init(x);
    memset(f,-1,sizeof(f));
    ans+=dfs(n,-2,1,1);
    cout<<ans;
    return 0;
}

那么其他的就差不多了。

总结:记忆化搜索!

然后你就可以AK LSYOI 上面的数位DP了

posted @   镜予歌  阅读(30)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示