AcWing 1082 数字游戏

题目描述:

科协里最近很流行数字游戏。

某人命名了一种不降数,这种数字必须满足从左到右各位数字呈非下降关系,如 123,446。

现在大家决定玩一个游戏,指定一个整数闭区间 [a,b],问这个区间内有多少个不降数。

输入格式

输入包含多组测试数据。

每组数据占一行,包含两个整数 a 和 b。

输出格式

每行给出一组测试数据的答案,即 [a,b]之间有多少不降数。

数据范围

1≤a≤b≤2^31−1

输入样例:

1 9
1 19

输出样例:

9
18

分析:

方法一:动态规划

首先按照y总解决数位DP问题的经典方法求解本题。我们需要求0到n中不降数的个数。则应该自高向低去枚举每一位,设n的高位向低位排列是an-1,an-2,...,ai,...,a1,a0。

则枚举第i位ai时,如果一个数的第i位大于等于0并且小于ai,则后面的位数不管怎么枚举都不会超过n了,只需要维持不降数的性质即可。因此我们需要知道以某个数开头的不降数的个数,状态表示:f[i][j]表示一个i位数最高位为j的不降数的个数,状态转移方程为f[i][j] += f[i-1][k],其中k大于等于j。所以我们可以先预处理出来十来位不降数的个数。

如果一个数的第i位等于ai(暗含着前面的位数也与n相等),如果ai要小于上一位的数字,则该方案不合法,否则继续枚举下一位数,当枚举到最后一位时正好是不降数,将总的方案数++即可。

这种分类方法可以处理很多数位DP问题,某一位小于固定的数时,后面的位置的数可以任意枚举,某一位等于固定的数时,应该继续递推直至最后一位为止。

#include <iostream>
#include <cstring>
#include <algorithm>
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 = j;k <= 9;k++){
                f[i][j] += f[i-1][k];
            }
            
        }
    }
}
int get(int n){
    if(n < 10)  return n + 1;
    vector<int> num;
    while(n)    num.push_back(n % 10),n /= 10;
    int res = 0,last = 0;
    for(int i = num.size() - 1;i >= 0;i--){
        int x = num[i];
        for(int j = last;j < x;j++)    res += f[i + 1][j];
        if(x < last)    break;
        last = x;
        if(!i)  res++;
    }
    return res;
}
int main(){
    int a,b;
    init();
    while(cin>>a>>b){
        cout<<get(b) - get(a - 1)<<endl;
    }
    return 0;
}

 方法二:记忆化搜索

如果说上面动态规划的方法没有那么直观的话,那么记忆化搜索可能更加清楚的描述了数位DP的递推过程,并且效率完全不输给DP的写法。我们从高位向低位枚举每位上的数字,正适合来个dfs,dfs的参数应该有哪些呢?首先应该有个u表示枚举到第几位了,并且通过u也能知道n对应的第u位上的数字。由于要求不降数,所以枚举第u位时还需要找到上一位的数字,所以还需要有个参数pre表示上一位的数字。另外,前面枚举的数字是小于n对应位的数字还是完全相等?这关系到我们在这位能枚举哪些数才不至于超过n,所以第三个参数应该是flag,表示前面枚举的数字是否已经小于了n对应位的数字。

在具体的dfs过程中,如果前面枚举的数字与n对应位数字完全相等,并且枚举到的当前位置上的数字不小于n对应位的数字时,就应该终止枚举了,枚举到的数字等于n对应位的数字,就将flag继续置1,dfs下一位。

如果当前dfs的flag是0,表示前面枚举到的数字已经小于了n,或者前面枚举到的数字等于n但是当前位置枚举到的数字小于n,就可以将flag置0,继续枚举下一位了。dfs的步骤比较简单,实现细节见代码:

#include <iostream>
#include <cstring>
using namespace std;
const int N = 12;
int len,num[N],f[N][N][2];
int dfs(int u,int pre,int flag){
    if(f[u][pre][flag] != -1) return f[u][pre][flag];
    if(!u)  return 1;
    int res = 0;
    for(int i = pre;i <= 9;i++){
        if(flag && i >= num[u]){
            if(i == num[u]) res += dfs(u - 1,i,1);
            break;
        }
        else    res += dfs(u - 1,i,0);
    }
    return f[u][pre][flag] = 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);
}
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/104486909