题意:
定义 不吉利的数字 为所有 含有 4
或 62
的号码。
例如:62315,73418,88914
都属于 不吉利号码。
但是,61152
虽然含有 6
和 2
,但不是连号,所以属于 吉利数字 之列。
给定区间 [a, b]
,问其中有多少个 吉利数字?
思路:
本题和之前几道数位dp的题目思考方式类似,
假设 数字 num
当前枚举到第 i
位,且 第 i
位数字是 x
,那么对于 答案的第 i
位数字 j
来说,有 两种填法:
-
1.
0 ~ x - 1
用last
表示num
中第i
位的 前面一位数字,
根据题意,j
不能是4
且 当last
是6
的时候j
不能为2
,两种情况在 枚举的时候特判 即可
之后,累加答案res
中,res += f[i+1][j]
-
2.
x
这一位则不用处理,更新last
即可:last = x
f[i][j] 状态表示:(核心)
- 集合:共有
i
位,且 最高位是j
的 满足吉利数定义 的数的集合 - 属性:数量
f[i][j] 状态计算:(核心)
第 i
位是 j
已固定,那么 考虑第 i - 1
位,假设为 k
,根据 吉利数 定义,j
为 6
时 k
不为 2
且 k
不能等于 4
状态转移方程 见下:
代码及注释:
#include<bits/stdc++.h>
using namespace std;
const int N = 15;
int f[N][N];
typedef vector<int> vi;
#define pb push_back
#define pp pop_back
void init()
{
//先预处理 1 位数的情况
for(int i=0; i<=9; ++i) f[1][i] = 1;
f[1][4] = 0;
//接下来预处理两位数及以上的情况
for(int i=2; i<N; ++i)
{
for(int j=0; j<=9; ++j)
{
for(int k=0; k<=9; ++k)
{
if(j==4 || k==4 || (j==6 && k==2)) continue;
f[i][j] += f[i-1][k];
}
}
}
}
int dp(int n)
{
if(!n) return 1;//n==0的时候显然不包含 62 or 4,算作 1 个合法方案
vi num; while(n) num.pb(n%10), n /= 10;
int res = 0;//存总方案数
int last = 0;//存当前位之前的数位信息 由于只有上一位数字是 6 且当前位数字为 2 的时候才需要特殊处理,因此 last 只需存储当前位的上一位是什么即可,last 初始化不为 6 即可,这里我们初始化为 0
for(int i=num.size()-1; i>=0; --i)
{
int x = num[i];
for(int j=0; j<x; ++j)//枚举左分支
{
if(j==4 || (j==2&&last==6)) continue;
res += f[i+1][j];
}
if(x==4 || (x==2 && last==6) ) break;//说明在右分支中,相邻两个数存在 62 或 4,即该分支不合法,直接 break
last = x;
if(!i) ++res;//如果走到了右分支末端且没有被 break,说明原数本身即为一个合法方案,将其加入答案 res 即可
}
return res;
}
int main()
{
init();
int l, r;
while(cin>>l>>r, l, r)
{
cout<<dp(r) - dp(l-1)<<'\n';
}
return 0;
}