数位dp(入门)

这一周一直在学dp,先学了一些简单的dp,然后又学了状压dp,这次又学了数位dp,唉,感觉这dp是真的难,可能是我太菜了吧,没办法,慢慢学吧,本来准备一周把dp学完是不太可能了,因为今天已经周六了,还是进入正题,说我们的数位dp吧。

数位dp是一种计数用的dp,一般就是要统计一个区间[le,ri]内满足一些条件数的个数。所谓数位dp,字面意思就是在数位上进行dp咯。数位还算是比较好听的名字,数位的含义:一个数有个位、十位、百位、千位......数的每一位就是数位啦!之所以要引入数位的概念完全就是为了dp。数位dp的实质就是换一种暴力枚举的方式,使得新的枚举方式满足dp的性质,然后记忆化就可以了。

这是抄的别人的解释,下面说一下我自己对数位dp的额理解吧,状态dp你运算的时候,是以每个数字所代表的二进制的每一位来为运算对象的,而数位dp呢,它是以数字的每一个数字为运算对象的,一个数字的个位,十位,百位……

解题思路:用数位dp的题一般都是让你求一个区间中,满足一些特征的数字的个数,例入求一个区间的奇数的个数……这样的题一般的数据的范围都比较大,就是让你用正常的暴力是根本写不出来的,就算写出来了那么也一定会超时,这样我们就需要一中特殊的方法进行暴力,那就是数位dp了,数位dp也是一种暴力,只不过他相当于一种记忆化的暴力,就是说它的暴力比较省时间,就像提前打好了表一样,这样一般都不壶超时了,

首先我们每次都可以先构造一个关于dp的二维数组dp[i][j]。代表的意思是一个数字的长度为i,以j数字开头的时候,所有符合特征的数字。递推关系 dp[i][j]=sum(dp[i-1][k]),这个数组求好后,如果我们再去求一个区间的一些特殊的数字的话,我们就可以缩小这个区间的范围了,

下面我们看一道题,来领略一节数位dp的魅力吧。

P2657 [SCOI2009]windy数

这个题的大致意思就是

windy定义了一种windy数。不含前导零且相邻两个数字之差至少为2的正整数被称为windy数。 windy想知道,

在A和B之间,包括A和B,总共有多少个windy数?

这个题就很符合我们所说的数位dp的特征了啊,下面给出AC代码。

#include<bits/stdc++.h>
using namespace std;
const int maxl=12;
int a[maxl];
int dp[maxl][maxl];
//dp[i][j]代表的意思是一个数字的长度为i,以j数字开头的时候,所有的windy数字
//递推关系 dp[i][j]=sum(dp[i-1][k]) abs(j-k)>=2;
void init()            //先把dp数组的值给求出来;
{
    memset(dp,0,sizeof(dp));
    for(int i=0;i<10;i++)
        dp[1][i]=1;
    for(int i=2;i<maxl;i++)
    {
        for(int j=0;j<=9;j++)
        {
            for(int k=0;k<=9;k++)
            {
                if(abs(k-j)>=2) dp[i][j]+=dp[i-1][k];
            }
        }
    }
}
int A(int n)
{
    memset(a,0,sizeof(a));
    int cnt=0,sum=0;
    while(n)          //分解n的每一个数字,并求出n的长度
    {
        cnt++;
        a[cnt]=n%10;
        n=n/10;
    }
    for(int i=1;i<cnt;i++)    //比这个数字长度小的数字都可以
        for(int j=1;j<=9;j++)
            sum+=dp[i][j];
    for(int i=1;i<a[cnt];i++)//长度相同,但是首位比n小的数字
        sum+=dp[cnt][i];
    for(int i=cnt-1;i>=1;i--) //剩余的部分,这一部分就有难了。
    {
        for(int j=0;j<=a[i]-1;j++)
        {
            if(abs(a[i+1]-j)>=2) sum+=dp[i][j];
        }
        if(abs(a[i]-a[i+1])<2) break;
    }
    return sum;
}
int main()
{
    int l,r;
    init();
    cin>>l>>r;
    cout<<A(r+1)-A(l)<<endl;
    return 0;
}
















 

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值