AcWing 1083 Windy数

题目描述:

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

Windy 想知道,在 A 和 B 之间,包括 A 和 B,总共有多少个 Windy 数?

输入格式

共一行,包含两个整数 A 和 B。

输出格式

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

数据范围

1≤A≤B≤2×10^9

输入样例1:

1 10

输出样例1:

9

输入样例2:

25 50

输出样例2:

20

分析:

方法一:动态规划 

本题是简单的数位DP问题,唯一要注意的是对前导0的处理。状态表示f[i][j]表示i为数最高位是j的windy数的个数,不过与上一题的表示不同的是,本题的i位数不包括前导零,也就是像014这样的数不会记入长度为i的windy数内,但是024会记入。这么说可能不明白,尽管去掉0后14是windy数,但是一旦再加上一位比如2014,显然就不是windy数了,而2024依旧是windy数,所以前导0在符合windy数性质情况下才会被记入f[i][j]。设n = abcde,在枚举最高位时,我们先不去枚举最高位是0的情况,在枚举完其他情况后再加上所有位数小于n的位数的windy数即可(因为这时的f数组会少统计0开头的windy数),而对于非最高位比如b所在的位置,当b不是0时,我们完全可以加上f[i + 1][0],因为只有前导0也合法的windy数才能被接入b所在位置的后面。举个例子就是n = 400时,枚举第一位是0的时候,f[3][0]只统计了024之类的合法windy数,并未统计014这样去掉前导0后合法的windy数,所以暂时不去统计首位是0的情况。而枚举第二位是0的情况时,比如203是合法的,201不合法,f[2][0]并未统计01这样不合法的windy数,所以可以加上。具体的实现细节都是同一套模板,不再赘述,见代码:

#include <iostream>
#include <cmath>
#include <vector>
using namespace std;
const int N = 12;
int f[N][N];
void init(){
    for(int i = 0;i <= 9;i++)   f[1][i] = 1;
    for(int i = 2;i < N;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];
            }
        }
    }
}
int get(int n){
    if(!n)  return 0;
    vector<int> num;
    while(n)    num.push_back(n % 10),n /= 10;
    int res = 0,last = -2;
    for(int i = num.size() - 1;i >= 0;i--){
        int x = num[i];
        if(x){
            for(int j = 1;j < x;j++){
                if(abs(j - last) >= 2)  res += f[i + 1][j];
            }
            if(last >= 2)  res += f[i+1][0];//不是最高位时,加上0开头的windy数
        }
        if(abs(x - last) < 2)   break;
        last = x;
        if(!i)  res++;
    }
    for(int i = 1;i < num.size();i++){//加上最高位是0的windy数
        for(int j = 1;j <= 9;j++){
            res += f[i][j];
        }
    }
    return res;
}
int main(){
    int A,B;
    cin>>A>>B;
    init();
    cout<<get(B) - get(A - 1)<<endl;
    return 0;
}

方法二:记忆化搜索

写完这题的记忆化搜索后,发现不仅DP的写法是一个套路,数位DP的dfs的写法也如出一辙,在上一题的基础上稍加改动即可。dfs的参数有当前枚举的位置u,上一个位置上的数pre,前面位置上枚举的数字是否已经小于了n的标志位flag。本题还要加一个参数zero表示之前枚举的位置是否都是0,如果都是0,那么枚举第u位就可以不受windy数规则的限制了,如果不是0,则还需要受规则的限制。

#include <iostream>
#include <cmath>
#include <cstring>
using namespace std;
const int N = 12;
int len,num[N],f[N][N][2][2];
int dfs(int u,int pre,int flag,int zero){
    if(f[u][pre][flag][zero] != -1) return f[u][pre][flag][zero];
    if(!u)  return 1;
    int t,res = 0;
    for(int i = 0;i <= 9;i++){
        if(!zero && abs(i - pre) < 2)    continue;
        t = 0;
        if(zero && !i)  t = 1;
        if(flag && i >= num[u]){
            if(i == num[u]) res += dfs(u - 1,i,1,t);
            break;
        }
        else    res += dfs(u - 1,i,0,t);
    }
    return f[u][pre][flag][zero] = res;
}
int get(int n){
    if(n < 10)  return n + 1;
    len = 0;
    while(n)    num[++len] = n % 10,n /= 10;
    memset(f,-1,sizeof f);
    return dfs(len,0,1,1);
}
int main(){
    int a,b;
    while(cin>>a>>b){
        cout<<get(b) - get(a - 1)<<endl;
    }
    return 0;
}
发布了311 篇原创文章 · 获赞 31 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/qq_30277239/article/details/104494687