数位dp裸题的套路

首先根据题目的要求初始化

一般为:

for(int i = 0;i <= 1;i ++) dp[1][i] = 1;
for(int i = 1;i <= 最大数的位数;i ++)
    for(int j = 0;j <= 9;j ++)
        for(int k = 0;k <= 9;k ++)
            if(......)//题目要求
                dp[i][j] += dp[i-1][k];

dp[i][j]表示不大于位数为i且最高位为j的方案总数。

但是对于最高位,我们并不总是能取得以它为开头的全集(19999……这一类除外)

不过,如果去掉最高位,我们所枚举的数都是合法的。所以可以直接枚举每一位1 ~ 9的答案就好了。

对于其他的数如果我们没有方法直接统计,或许我们要想办法把他拆开。

举个例子,对于 24653这组数。

因为我们有5位数,所以对于前四位是一定会包含的。

所以我们枚举之前每一位数以1~9为最高位的答案(若不允许含前导0)。

在这里,对于最高位,我们只加上以1位最高位的答案。(因为以2为最高位并不能枚举到所有的数)

然后我们假定最高位为2,加上次高位以0~3为下一位的答案。

然后我们假定前面的数为24,……

……

然后对于最后一位的3我们怎么处理,难道这个3不要了?

那就不要了吧,写开区间吧,我们求24654这个数。

对了,有一个细节,如果对于某一枚举的数位自身已经不符合要求了。

由于我们的每一位都会带到下一位,所以后面的一定不符合条件,要直接跳出。

例如我们要求不大于某一个数中相邻两个数不大于2的个数,对于12347666这个数如果我们已经假定前几

位分别是12347,那么对于后面的数无论我们选什么都是不成立的,所以直接break;

附上windy数的代码及题目链接:

http://www.lydsy.com/JudgeOnline/problem.php?id=1026

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
using namespace std;
int a,b,ans,cnt,jl[105],dp[105][105];
void init()//数位dp的套路,按照题目要求初始化。 
{
    for(int i = 0;i <= 9;i ++) dp[1][i] = 1;//初始化的初始化 
    for(int i = 2;i <= 10;i ++)
        for(int j = 0;j <= 9;j ++)
            for(int k = 0;k <= 9;k ++)
                if(abs(j - k) >= 2)
                    dp[i][j] += dp[i-1][k];
}
int tj(int x)//这是一个开区间的。 
{
    cnt = 0;ans = 0;
    while(x) {jl[++cnt] = x % 10;x /= 10;}//拆分x 
    for(int i = 1;i < cnt;i ++)
        for(int j = 1;j <= 9;j ++)
            ans += dp[i][j];//如果去掉最高位,我们所枚举的数都是合法的。 
    for(int i = 1;i < jl[cnt];i ++) ans += dp[cnt][i];//最高位到第jl[cnt-1]位为止还是全集。 
    for(int i = cnt - 1;i >= 1;i --)//假设上一位已经确定为jl[i+1]。 
    {
        //由于我们记录的都是全集而实际某些数不一定能达到,为了保证达到,最高位不枚举自身。
        //对于最高位,我们就假设我们确定了,而在下一位枚举它。 
        for(int j = 0;j < jl[i];j ++) 
            if(abs(jl[i+1]-j) >= 2)
                ans += dp[i][j];
        //如果这个数字已经有两位矛盾了,后面的再枚举也是不对的。 
        if(abs(jl[i+1]-jl[i]) < 2) break;
    }
    return ans;
}
int main()
{
    init();
    scanf("%d%d",&a,&b);
    cout<<tj(b+1) - tj(a);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值