[SCOI2009] windy 数

题面

题目描述

不含前导零且相邻两个数字之差至少为 $ 2 $ 的正整数被称为 windy 数。windy 想知道,在 $ a $ 和 $ b $ 之间,包括 $ a $ 和 $ b $ ,总共有多少个 windy 数?

输入格式

输入只有一行两个整数,分别表示 $ a $ 和 $ b $。

输出格式

输出一行一个整数表示答案。

样例#1        样例#2

输入:1 10         输入: 25 50
输出: 9          输出:20
数据范围:
对于所有的数据,满足 $ 1 \leq a \leq b \leq 2 \times 10^9 $

思路:

首先应该想到的就是数位DP,数位DP就是用来解决形如“在 $ l $ 到 $ r $ 的区间里有多少个数满足题目要求”的问题。

这个题题面的意思大概就是像 $ 183,691 $ 这样的相邻数字之间的差是 $ 2 $ 的数是windy数。

暴力的话显然是不行的,这道题的数据范围很大,从 $ a $ 枚举到 $ b $ 时间复杂度可想而知。

那用数位DP的话怎么做呢?显然这个题有那么点区间DP的意思,但是区间DP并不能解决此题(状态设计出来了没法高效地转移)。

数位DP的状态设计就是 $ f[i][j] $ 表示前$ i $位中最高位为 $ j $ 的windy数的个数。显然 $ f[i][j]= \sum_{|k-j| \geq 2} f[i-1][k] $ (这个是因为此类DP的原理其实就相当于一位一位地往上加数)

状态和转移方法都有了,那该怎么得出题目所要求的东西呢?

我们可以考虑利用前缀和的思想得出该结果。直接求区间显然不怎么现实,那我们就可以分别求出 $ 1-(l-1) $ 和 $ 1-r $ 区间的和,再相减就可以得出结果。

那又怎么求出 $ 1-x $ 区间的和呢?这里直接求又是不行的。考虑分类讨论。以 $ x[i] $ 表示 $ x $ 这个数的第 $ i $ 位, $ cnt $ 表示 $ x $ 的位数。

一、求出 $ cnt-1 $ 位的windy数的个数

因为我们要讨论 $ 1-x $ 这个区间,所以 $ cnt-1 $ 位的windy数是完全被包括在这个范围内的,因此可以直接求解

二、求出有 $ cnt $ 位且最高位小于 $ x[0] $ 的windy数。

这些windy数显然也是也是在上述范围内的,可以直接累加到答案中。这部分答案即为: $ \sum_{1 \leq x[0] \leq 9} f[cnt][x[0]] $

三、类比二、中的过程。

依次求出有 $ cnt-1 $ 位、最高位为 $ x[1] $ 的windy数,有 $ cnt-2 $ 位、最高位为 $ x[2] $ 的windy数…………有 $ 1 $ 位、最高位为 $ x[cnt] $ 的windy数。每向下推进一位,前面的数字(更高位的数字)就已经固定,即为 $ x $ 这个数的前\(t\)位。
这里需要注意控制一个边界条件。就是当以 $ x[t] $ 为最高位时,若 $ |x[t]-x[t+1]| < 2 $ ,那么最高位为 $ x[t] $ ,次高位为 $ x[t+1] $ 的windy数不存在,直接退出。

Code:

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
typedef long long ll;
int a, b;
int f[11][11];
inline int read(void){
    int f = 1, x = 0;char ch;
    do{ch = getchar();if(ch=='-')f = -1;} while (ch < '0' || ch > '9');
    do{ x = x * 10 + ch - '0';ch = getchar();} while (ch >= '0' && ch <= '9');
    return f * x;
}
inline int _abs(int x) { return x <= 0 ? -x : x; }
inline void _init(void){
    for (int i = 0; i <= 9;++i)
        f[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) f[i][j] += f[i - 1][k];//预处理出所有的windy数的个数
        }
    }
    return;
}
inline int calc(int k){
    if(k==0) return 0;
    int res = 0, cnt = 0, num[11] = {0};
    while(k){
        num[++cnt] = k % 10;
        k /= 10;
    }//将读入的数分解
    for (int i = 1; i < cnt;++i){
        for (int j = 1; j <= 9;++j)
            res += f[i][j];//求出cnt-1位的windy数的个数,可以直接累加
    }
    for (int i = 1; i < num[cnt];++i)
        res += f[cnt][i];//处理出所有cnt位、且最高位小于读入数字最高位的windy数的个数
    for (int i = cnt - 1; i >= 1;--i){//最难搞的一块,处理其余的情况
        for (int j = 0; j < num[i];++j)
            if(_abs(j-num[i+1])>=2) res += f[i][j];
        if(_abs(num[i+1]-num[i])<2) break;//不要忘了判断是否合法
    }
    return res;
}
int main(){
    a = read(), b = read();
    _init();
    printf("%d\n", calc(b) - calc(a - 1));//前缀和处理
    // std::cout << '\n' << calc(b) << ' ' << calc(a - 1);
    return 0;
}
posted @ 2020-07-22 08:39  Shadow_hyc  阅读(135)  评论(0)    收藏  举报