数字游戏
题目描述
核心思路
对于一个整数n,我们把它的每一位都抠出来,从最高位开始考虑:
假设原数n中第i位的数为x,它上一位的数为j,那么当我们枚举模拟时,第i位可以分为两个分支,左边分支可以填0到x-1中的某个数,右边分支是填x这个数。
- 考虑左分支的方案数:假设我们从0到x-1中选出了某个数k( j ≤ k < x , 保 证 非 下 降 ) j\leq k<x,保证非下降) j≤k<x,保证非下降),填到了第i位,则方案为 f [ i ] [ k ] f[i][k] f[i][k]表示一共有i位,且最高位为k的方案数。那么左分支的方案数为 ∑ j = l a s t x − 1 f [ i ] [ k ] \sum \limits _{j=last}^{x-1}f[i][k] j=last∑x−1f[i][k]。
- 考虑右分支的方案数:如果 x < l a s t x<last x<last,无法满足非下降条件, 直接break, 否则填x然后从右分支又开辟出左右分支,继续判断下一位的情况
- 如果i == 0, 可以填任意数,也是一种方案, res++
代码
写法1
#include<iostream>
#include<vector>
using namespace std;
const int N=15;
int f[N][N];///f[i][j]表示最高位是j并且一共有i位的不降数的方案数
//预处理出最高位是j并且一共有N位的不降数的方案数
void init()
{
//如果只有1位,那么这个数肯定是满足不下降性质的,即对于只有个位的话,那么0到9这十个个位数字都是合法的方案,且方案数是1
for(int j=0;j<=9;j++)
f[1][j]=1;
//从有2位一直枚举到有N位
for(int i=2;i<N;i++)
{
for(int j=0;j<=9;j++)//枚举当前位的最高数可以填啥
{
//k是j的下一位,j是k的上一位,由于j<=k才能满足不下降的性质,因此k应该从j开始取
for(int k=j;k<=9;k++)
{
f[i][j]+=f[i-1][k];
}
}
}
}
int func(int n)
{
//n=0,0满足不下降性质,是一个合法的方案
if(!n)
return 1;
vector<int>nums;
while(n)
{
nums.push_back(n%10);
n/=10;
}
int res=0;//记录方案数
int last=0;//记录上一位的数是多少
//从高位向低位枚举
for(int i=nums.size()-1;i>=0;i--)
{
int x=nums[i];//取出第i位上的数
//要保证比下一位>=上一位,所以从last开始枚举,最多枚举到x-1,
//last为上一位的数,对下一位的枚举是有限制的
//这里不能写成res+=f[i][j],因为比如原数n=1934,存入num向量中就是4391对应下标就是0123
//如果写成f[i][j],那么当下标i=3时,求的就是f[3][j]含义就是最高位是j并且一共有3位的方案数
//但实际是一共有4位,因此是f[i+1][j]而不是f[i][j]
for(int j=last;j<x;j++)//计算左边的分支
{
res+=f[i+1][j];///左端的节点有i+1个位数(因为第一位的下标是0)
}
//如果下一位的数x比上一位的数last还要大,则不满足不下降性质,提前退出
//特判一下: 如果当前数比前一个数小,不能进入右边的分支
if(x<last)
break;
//说明下一位的数x>=last,那么就更新last
//进入右边的分支
last=x;
//如果能顺利到最后一个数说明,树的最右边这一段的每一个数都是小于等于前一位数的,因而++
if(!i)//最后再加上最右边末端的分支
res++;
}
return res;
}
int main()
{
init();
int l,r;
while(cin>>l>>r)
{
cout <<func(r)-func(l-1)<<endl;
}
return 0;
}
写法2
#include<iostream>
using namespace std;
const int N=12;
int a[N];//把整数的每一位数字都抠出来,存入a数组中
int f[N][N];//f[i][j]表示一共有i位,且最高位数字是j的不降数的个数
//预处理不降数的个数
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=j;k<=9;k++)
{
if(k>=j)
f[i][j]+=f[i-1][k];
}
}
}
}
int func(int n)
{
//特判,如果n==0,则返回1
if(!n)
return 1;
int cnt=0;//数组a中的元素个数
while(n)
{
a[++cnt]=n%10;
n/=10;
}
int res=0;//答案
int last=0;//上一位的数字
//从高位枚举到低位
for(int i=cnt;i>=1;i--)
{
int now=a[i];//取出当前位的数字
//处理左分支,可选的数是[0,x-1]
for(int j=last;j<now;j++)//枚举当前位可以填入的数字
{
res+=f[i][j];//累加方案数
}
//处理右分支
if(now<last)//剪枝
break;
last=now;//更新last
//如果走到了最右下角,则说明n这个数它自身也是满足的
if(i==1)
res++;
}
return res;
}
int main()
{
init();
int l,r;
while(cin >>l>>r)
cout <<func(r)-func(l-1)<<endl;
return 0;
}