数位--dp

这几天在看数位dp,总算稍微有一丢丢的看明白了,所以就来浅浅的分享一下吧!!!

概况

数位dp主要是来求一个区间满足条件的个数的
比如 :从10到20之间求1的个数。显然有11个,但是在做题的时候范围会很大像是1e18 之类的 ,所以通常的方法就很容易超范围所以数位dp是一个不错的选择。

基本思想

假设范围是n~m,我们可以通过dp求出1到n的满足条件的个数,在求出1到m满足条件的个数。两者相减就是范围内满足条件的个数。

 long long  int n,m;
    cin>>n>>m;
    cout<<solve(m)-solve(n-1)<<endl;

大体的思想就是,我们通过n分解成每一位数,从最高位开始枚举。
比如:234;
如果最高位枚举2的话十位则枚举的范围是(0~3)
如果最高位枚举的是(0 ~ 1)时,十位则枚举范围是(0 ~ 9)之后随着位数以此类推

   2               3               4
   
   2               3             (0~4)

   2             (0~2)           (0~9)

 (0~1)           (0~9)           (0~9)

采用这个枚举方式进行记忆化搜索;

模板

数位dp感觉还是蛮容易想的如果你知道模板

ll dp[20][20]; //  为了记忆化搜索储存 
ll  arr[100];  //为了统计每一位数的值,所以数组大小不用太大。
//pos为位数,就是dfs执行到一个数哪一位。
//sta是状态,状态不同的题状态不一样
// land为前导0 比如从0到12345  则1就表示成00001,这些0就是前导0
//limit是为了记录上一位的值是不是最大值,好为了判断这一位值的范围 比如123 十位如果执行到2 则各位范围只能是0~3,如果十位是(0~1)则各位可以是(0~9)
ll dfs(int pos,int sta,int land,int limit)
{                     // land为1则这一位是前导0
                      // limit为1则表示执行到这个数这一位的最大值
    if(pos==-1)return 1;//表示一个数执行完成了 返回值不一定是1,根据题目会有所改变。
    if(!limit&&!land&&dp[pos][sta]!=-1)return dp[pos][sta]; 
     //记忆化存储  枚举到当前位置 pos,状态为 sta 的数量,dp 值保存的是满足条件数的个数

    int up=limit?arr[pos]:9;//枚举这一位的上界
   long long  int ret=0;
    for(int i=0;i<=up;i++)
    {
        if()
        。。。。。
        if(....)
        。。。。。       

       ret+=dfs(pos-1,sta(/*状态的变形*/),land&&i==0,limit&&i==arr[pos]);
      //进行搜索
    }
    if(!land&&!limit) dp[pos][sta]=ret;//搜索完成储存数目
    return ret;

}

ll solve(ll x)
{
    int pos=0;
    while(x)//分解位数
    {
        arr[pos++]=x%10; //储存上界的每一位
        x/=10;
    }
    return dfs(pos-1,0,1,1);
}

int main()
{
  ll n,m;cin>>n>>m;
  memset(dp ,-1,sizeof dp);  //进行初始化
 
  cout<<solve(m)-solve(n-1<<" ";
  cout<<endl;

}
注意:

(1)前导0的使用方法
当初看的时候就是感觉这个前导0好像在这串代码里没啥用的样子。但是确实有用(hhhh) 上面说了我们给每一个数都在前面加了前导0让他和最大数的位数相同。这里前导0有可能扰乱我们的基数
比如:我们来计数(1~123)的0的个数
1则就相当于001,1前面的前导0 就会扰乱我们对0的计数。
(2)我们的状态需要自己定义,这还是一个难点对我来说,还有就是那个pos==-1返回的值不是一成不变的根据题意稍作修改。
(3)还要注意dp数组还有里面的个别变量会超过int类型的最大值。所以需要把它们设置成 long long

例题

(1)P4999 烦人的数学作业
求给定区间内每个数的每一位数之和的和;
比如 123~124=1+2+3+1+2+4
代码:

#include<bits/stdc++.h>
#define INF 0x7f7f7f7f
#define MAXN 100005
using namespace std;

long long  int n,m,f[408][408],arr[1000109],c[100009],sum[409],dp[408][408];
long long int mod=1e9+7;
long long dfs(long long  int pos,long long int sum,int limit)
{
    if(pos==-1)return sum;
    if(!limit&&f[pos][sum]>=0)return f[pos][sum];
    int up=limit?arr[pos]:9;
    int temp=0;
    for(int i=0;i<=up;i++)temp=(temp+dfs(pos-1,sum+i,limit&&i==up))%mod;
    if(!limit)f[pos][sum]=temp;
    return temp;
}

long long  int solve(long long  int x)
{
   long long int pos=0;
    while(x)
    {
        arr[pos++]=x%10;
        x/=10;
    }
    return dfs(pos-1,0,1)%mod;
}
int main()
{
  int t;
    cin>>t;
    memset(f,-1,sizeof f);
    while(t--)
    {
       long long  int n,m;
        cin>>n>>m;
        cout<<(solve(m)-solve(n-1)+mod)%mod<<endl;

    }
}

(2)P2602 [ZJOI2010]数字计数
给定范围内出现(0~9)的次数
代码:


#include<bits/stdc++.h>
#define INF 0x7f7f7f7f
#define MAXN 100005
#define   ll long long
using namespace std;

ll dp[20][20][20];
ll  arr[10000];
ll dfs(int pos,int sta,int land,int limit,int number)
{
    if(pos==-1)return sta;
    if(!limit&&!land&&dp[pos][number][sta]!=-1)return dp[pos][number][sta];
    int up=limit?arr[pos]:9;
   long long  int ret=0;
    for(int i=0;i<=up;i++)
    {
        if(land&&i==0)
            ret+=dfs(pos-1,sta,land&&i==0,limit&&i==arr[pos],number);
        else
             ret+=dfs(pos-1,sta+(number==i),land&&i==0,limit&&i==arr[pos],number);

    }
    if(!land&&!limit) dp[pos][number][sta]=ret;
    return ret;

}

ll solve(ll x,int number)
{
    int pos=0;
    while(x)
    {
        arr[pos++]=x%10;
        x/=10;
    }
    return dfs(pos-1,0,1,1,number);
}

int main()
{
  ll n,m;cin>>n>>m;
  memset(dp ,-1,sizeof dp);
  for(int i=0;i<10;i++)
  cout<<solve(m,i)-solve(n-1,i)<<" ";
  cout<<endl;
}

这两个题一个使用了前导0,一个 没有使用,细细品味!!!

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

晨晓翔同学

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值