题目描述:
科协里最近很流行数字游戏。
某人命名了一种不降数,这种数字必须满足从左到右各位数字呈非下降关系,如 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;
}