Windy数

Windy数

题目描述

在这里插入图片描述


核心思路

题目说不含前导0,比如15是Windy数,加上前导0,那么015就不是Windy数了。数位DP的解题套路:

  • 利用前缀和思想,欲求区间[l,r]中含有Windy数的个数,那么我们可以先求出区间[0,r]中含有Windy数的个数,然后再求出区间[0,l-1]中含有Windy数的个数,相减得到的就是区间[l,r]中含有的Windy数的个数了。
  • 分类填数。设整数 x x x一共有n位,设 x x x表示为 a n a n − 1 a n − 2 ⋯ a 2 a 1 a_na_{n-1}a_{n-2}\cdots a_2a_1 anan1an2a2a1,从高位到低位依次枚举填数。因为不含前导0,因此最高位就只能填1到 a n a_n an,其他位可以填0到 a i a_i ai。每个位上填数时,有两个分支,分为两类:左分支是0到 a i − 1 a_i-1 ai1 a i a_i ai,这样填数就可以保证不超过 x x x

在这里插入图片描述

为什么预处理时,对于i=0还需要处理呢?即为什么是i=0;i<=9;i++而不是i=1;i<=9;i++呢?

比如 f [ 2 ] [ 2 ] f[2][2] f[2][2]表示一共有两位数字,且最高位数字是2(即十位数字是2),设原数为22,那么最低位(即个位)可以是0,1,2。对于最低位是0时,得到数字20,很明显这个数字是满足Windy数的。统计 f [ 2 ] [ 2 ] f[2][2] f[2][2]时会用到 f [ 1 ] [ 0 ] f[1][0] f[1][0]。因为当进入if语句时,会执行 f [ i ] [ j ] = f [ i ] [ j ] + f [ i − 1 ] [ k ] f[i][j]=f[i][j]+f[i-1][k] f[i][j]=f[i][j]+f[i1][k],即 f [ 2 ] [ 2 ] = f [ 2 ] [ 2 ] + f [ 1 ] [ 0 ] f[2][2]=f[2][2]+f[1][0] f[2][2]=f[2][2]+f[1][0],等式右边的 f [ 2 ] [ 2 ] f[2][2] f[2][2]取的是全局的0,假设我们不对 f [ i ] [ 0 ] f[i][0] f[i][0]做预处理,那么此时 f [ i ] [ 0 ] f[i][0] f[i][0]用的也是全局的0,也就是说 f [ 1 ] [ 0 ] = 0 f[1][0]=0 f[1][0]=0。那么得到的 f [ 2 ] [ 2 ] = 0 + 0 = 0 f[2][2]=0+0=0 f[2][2]=0+0=0,那么就会错过数字20没有被算进去了。

如何理解last初始化为-1呢?

首先这个数不含前导0,那么一个数的最高位就肯定是从 ≥ 1 \geq 1 1开始了,为了使得能够从最高位开始进入循环,我们来看这个条件:if(abs(j-last)>=2),对于当前的最高位,如果来到了这个语句,说明j=1,那么要想使得进入if语句,则要满足abs(j-last)>=2,即abs(1-last)>=2,因此解得 l a s t ≤ − 1 last\leq -1 last1,于是只要last的初始值是 ≤ − 1 \leq-1 1的就行了。

如何理解j=(i==nums.size()-1)呢?

这句话的意思是说,nums.size()-1是最高位,如果 i = = n u m s . s i z e ( ) − 1 i==nums.size()-1 i==nums.size()1,说明i指向的是最高位,如果i指向的是最高位,那么 i = = n u m s . s i z e ( ) − 1 i==nums.size()-1 i==nums.size()1就成立,返回1,因此j=1,那么以后就处理后面的低位了,不会再判断是否有 i = = n u m s . s i z e ( ) − 1 i==nums.size()-1 i==nums.size()1了,而是j++了。如果不成立则返回0,说明最高位是0.写这句话其实就是为了特殊处理最高位是0的情况。其实也可以这么写:

int flag==num.size()-1;
for(int j=flag;j<x;j++)

在这里插入图片描述

如何处理不含前导0的情况呢?

低于n位数的数要特判,首位数不能是0。

处理前导0的代码是什么意思,低于n位数的数要特判是什么意思?

在这里插入图片描述

和数字游戏的区别:数字游戏它的左分支是0~x-1,可以取0,也就是说数字游戏它的高位可以是0,比如0013,也就是说数字游戏它是再处理位数是nums.size()的时候,就已经把小于位数小于num.size()的情况算进去了,比如0013,虽然处理位数看起来是4位,但有前导0,真正有效的位数是是后面两位(13),因此,数字游戏那一题就不需要对含有前导0(可以理解为位数小于num.size的数)特殊处理了。但是这一题不一样,题目说了Windy数是不含有前导0的,比如0013,它含有前导0,它的位数是4位,但是13它是满足Windy数的,它的位数是2位,因此,我们需要对小于4位的0013来看看有没有满足Windy数的,因此,最后需要特殊处理位数小于num.size的数。


代码

写法1

#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
//因为题目给的整数最大是2e9,那么最多是10位,所以这里开大一些取11位
const int N=11;
int f[N][N];//f[i][j]表示一共有i位且最高位数字是j的Windy数的个数
//预处理出Windy数的个数
void init()
{
    for(int j=0;j<=9;j++)
    f[1][j]=1;
    for(int i=2;i<N;i++)//阶段:枚举位数,从有2位数一直枚举到有N位数
    {
        for(int j=0;j<=9;j++)//枚举前面一位
        {
            for(int k=0;k<=9;k++)//枚举后面一位
            {
                if(abs(k-j)>=2)
                f[i][j]+=f[i-1][k];
            }
        }
    }
}
int func(int n)
{
    //特判n是0,则返回0
    if(!n)
    return 0;
    vector<int>nums;
    int res=0;//答案
    int last=-1;//记录上一位的数字
    while(n)
    {
        nums.push_back(n%10);
        n/=10;
    }
    //处理位数是nums.size()的数
    for(int i=nums.size()-1;i>=0;i--)
    {
        int x=nums[i];//取出第i位的数
        //处理左分支,可选的数是0<=j<=x-1
        for(int j=(i==nums.size()-1);j<x;j++)
        {
            //比如原数num=7598 下标就是[3,2,1,0] 当前i=3,假设我们写成了f[i][j],即f[3][j]
            //那么就表示一共有3位且最高位数字是j的Windy数的个数 但是很明显7598是4位
            if(abs(j-last)>=2)
            res+=f[i+1][j];
        }
        //处理右分支,右分支选定了x这个数,则右分支固定了x这个数,如果满足条件,则可以往右分支走
        if(abs(x-last)<2)//剪枝
        break;
        last=x;//更新last
        if(!i)//如果成功走到了最右下角,说明n这个数它自身也是一个Windy数
        res++;
    }
    //处理位数是小于nums.size()的数,这里可以看作是特殊处理有前导零的数
    //因为本题中特殊规定了,windy数不能包含前导零。如果把位数较低的数看成是包含前导零,那么首位就不能是1
    //但windy数的首位可以是1。
    for(int i=1;i<nums.size();i++)//i是枚举位数  从有1位枚举到有nums.size()-1位
    {
        //由于是特殊处理有前导零的数,因此j不是从0开始,而是从1开始,表示最高位是>=1
        for(int j=1;j<=9;j++)
        {
            //为什么不是写成f[i+1][j],那是因为i的下标从1开始取了,如果从0开始取,那么还是要写成f[i+1][j]
            //比如原数num=598,那么下标为[3,2,1] 假设此时i=3,如果我们写成了f[i+1][j],即f[4][j],
            //那么表示一共有4位且最高位数字是j的Windy数的个数,但是很明显598只有3位
            res+=f[i][j];
        }
    }
    return res;
}

int main()
{
    init();
    int l,r;
    scanf("%d%d",&l,&r);
    cout <<func(r)-func(l-1)<<endl;
    return 0;
}

写法2

#include<iostream>
#include<algorithm>
using namespace std;
const int N=11;
int a[N];
int f[N][N];
void init()
{
    for(int j=0;j<=9;j++)
    f[1][j]=1;
    for(int i=2;i<N;i++)
    {
        for(int j=0;j<=9;j++)
        {
            for(int k=0;k<=9;k++)
            {
                if(abs(k-j)>=2)
                f[i][j]+=f[i-1][k];
            }
        }
    }
}
int func(int n)
{
    //特判,如果n==0,则返回0
    if(!n)
    return 0;
    int cnt=0;//数组a中的元素个数
    while(n)
    {
        a[++cnt]=n%10;
        n/=10;
    }
    int res=0;//答案
    int last=-1;//记录上一位的数字
    //从高位枚举到低位
    for(int i=cnt;i>=1;i--)//答案是cnt位的
    {
        int now=a[i];//取出第i位的数字
        int flag=i==cnt;//判断是否是最高位
        //走左侧分支,可选数的范围是[j,now-1]
        for(int j=flag;j<now;j++)
        {
            if(abs(j-last)>=2)
            res+=f[i][j];//累加答案
        }
        //走右侧分支
        if(abs(now-last)<2)//剪枝
        break;
        last=now;//更新last
        //如果走到了最右下角,说明n这个数它自身也是Windy数
        if(i==1)
        res++;
    }
    //答案小于cnt位的
    for(int i=1;i<cnt;i++)
    {
        for(int j=1;j<=9;j++)
        res+=f[i][j];
    }
    return res;
}
int main()
{
    init();
    int l,r;
    scanf("%d%d",&l,&r);
    cout <<func(r)-func(l-1)<<endl;
    return 0;
}

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

卷心菜不卷Iris

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

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

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

打赏作者

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

抵扣说明:

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

余额充值