数位dp

℡╲_俬逩灬. 提交于 2020-11-16 06:57:40

数位DP

经典的数位Dp是要求统计符合限制的数字的个数。

一般的形式是:求区间[n,m]满足限制f(1)、f(2)、f(3)等等的数字的数量是多少。条件 f(i)一般与数的大小无关,而与数的组成有关。

善用不同进制来处理,一般问题都是10进制和二进制的数位dp。

数位dp的部分一般都是很套路的,但是有些题目在数位dp外面套了一个华丽的外衣,有时我们难以看出来。

例题:windy数

                             

 

 

 精华都在代码的注释里呢,好好看吧

代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll l,r;
int num[20];
ll f[20][20];/*按照题目定维数*/ 

ll dfs(int i,int last,bool have,bool lim)
/*需要传递的各种参数,视题目而定*/
/*本题中分别表示当前到了第几位,上一位是什么,
有没有前导0,有没有顶上界*/
/*一般需要有的是位数,上界,其他视情况而定*/ 
{
    if(i==0) return 1;/*判断是否已经枚举完,返回1就表示你枚举的数是合法的*/ 
    if(!have&&!lim&&f[i][last]!=-1)/*判断边界条件以及是否已经搜索过*/ 
     return f[i][last];
    int up=0;/*这一位枚举的上界*/ 
    if(lim==1) up=num[i];/*上一位顶到上界的话对这一位就有限制*/ 
    else up=9;/*如果上一位没有顶到上界的话这一位就可以随意*/ 
    ll ans=0;
    int op=0;/*表示这一位选择的数是什么*/ 
    for(int j=0;j<=up;j++)
    {
        if(abs(j-last)<2) continue;/*判断不合法条件*/ 
        op=j;
        if(have&&j==0) op=-2;
        ans+=dfs(i-1,op,op==-2,lim&&j==up);/*继续记搜,一定要注意各种限制条件的传递*/ 
    }
    if(!have&&!lim) f[i][last]=ans;/*在一定条件下记录记搜的答案*/ 
    return ans;
}

ll work(ll x)
{
    memset(num,0,sizeof(num));
    int tot=0;
    while(x)/*把数字的各个位拆到数组中存储*/ 
    {
        num[++tot]=x%10;
        x/=10;
    }
    memset(f,-1,sizeof(f));
    return dfs(tot,-2,1,1);
}

int main()
{
    scanf("%lld%lld",&l,&r);
    printf("%lld",work(r)-work(l-1));/*前缀和思想*/ 
}

 

来看另一道题

洛谷P4317 花神的数论题

                      

 

 

 

题意就是说求1~n每个数二进制含有1的个数的乘积

也是数位DP

设f[i][j]表示前i位有j个1的方案数

然后记忆化搜索

代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod=10000007;
ll n;
ll f[51][51];
int num[51];

ll dfs(int i,int cnt,bool lim)
{
    if(i==0) return max(cnt,1);
    if(!lim&&f[i][cnt]!=-1) return f[i][cnt];
    ll ans=1;
    int up=0;
    if(lim==1) up=num[i];
    else up=1;
    for(int j=0;j<=up;j++)
    {
        ans=(ans*dfs(i-1,j==1?cnt+1:cnt,lim&&j==up))%mod;
    }
    if(!lim&&f[i][cnt]==-1) f[i][cnt]=ans;
    return ans;
}

inline void work(ll x)
{
    int cnt=0;
    while(x)
    {
        num[++cnt]=x%2;
        x/=2;
    }
    cout<<dfs(cnt,0,1);
}

int main()
{
    scanf("%lld",&n);
    memset(f,-1,sizeof(f));
    work(n);
}

 

关于数位dp的经验

1:注意很多时候带进去是n==0要特殊处理。

2:还有一般问[m,n],我们求[1,n]-[1,m-1]但是有的时候m为0就炸了。

3:求所有包含49的数,其实就是(总数-所有不包含49的数)。前者的化需要有两维限制,一个是上一位是什么,一个是之前有没有49。但是后 者只需要记一个上一位是什么。就能好写一些。

4:一般问题的数位dp部分,都是套路,但是这并不代表它外面“华丽的外衣”和与其他算法结合的的部分也是无脑的。要看出它是考数位dp,要看出问题怎么变化一下就是数位dp了。

5:dp初始化memset要置为-1。不能置为0!!!!!!因为有很多时候dp值就应该是0,然后我们如果误以为是因为之前没有计算,重新计算的话,就会tle。

6:既然是记忆化搜索,那就可以剪枝!!!!可行性剪枝!!

7:注意windy数的情况,有时前导0也需要记的!!! 

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!