题意
包含 4 或 62 字段的数字称为不吉利数字。统计 [n, m] 中吉利数字的数目。
思路
数位DP问题。
dp[i][j] (j 取 0 ~ 2)表示 [0, 10^i)区间内 吉利数的个数、吉利但第一位是2、不吉利的个数。转移方程不难写出,见AC代码部分注释即可。
关键的地方在于怎么处理 [n, m] 的限制。也是在这个题中学到的地方。这里要感谢百度文库里一个ppt,讲解的很清晰。
首先把 [n, m] 转化位 [0, m + 1) - [0, n)。
对于一个小于 n 的数,肯定是从高位到低位出现某一 < n 的对应位。如果某一位比 n 的对应位小,则后面的位可以 0 ~ 9 而没有限制。具体做法就是,枚举位 i ,前面的位固定等于 n 的对应位,后面的位则可以取到 0 ~ 9 而没有限制。
注意的几个点:
1.dp转移的方向和区间统计的方向是相反的。具体到本题中,dp转移方向从低位向高位,区间统计方向从高位向低位。
2.注意左闭右开的区间。
3.本题中,吉利数字数量过多,可以统计不吉利数字然后减去。
题目链接
http://acm.hdu.edu.cn/showproblem.php?pid=2089
AC代码
#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;
int n, m;
int A[10]; //把数字的每一位分开
int dp[10][3]; //dp[i][0] 前i位中不含不吉利数字的个数;dp[i][1] 不含不吉利且第i位是2;dp[i][2] 含不吉利
void init() //计算dp数组(无大小限制,每一位都能取0 ~9)
{ //特别的,从低位向高位算,与后面的统计顺序相反
memset(dp, 0, sizeof dp);
dp[0][0] = 1;
for(int i= 1; i<= 6; i++)
{
dp[i][0] = dp[i - 1][0] * 9 - dp[i - 1][1]; //吉利数前加上一个4以外的数都是吉利数,但是i-1位首位是2时不能加6
dp[i][1] = dp[i - 1][0]; //吉利数前加上2构成吉利数且首位是2
dp[i][2] = dp[i - 1][2] * 10 + dp[i - 1][0] + dp[i - 1][1]; //不吉利数前加任一数都不吉利,吉利数前加4不吉利,i-1位是2前加6也不吉利
}
}
int solve(int x) //计算[0,x)中的吉利数字,注意左闭右开
{
int left = x, len = 0; //把x各位拆开,len~1高位到低位
while(left)
{
A[++ len] = left % 10;
left /= 10;
}
A[len + 1] = 0; //最高位的高一位看作0
int res = 0; //实际上记录的是不吉利数的个数
bool flag = false; //前n位是否已经不吉利
for(int i= len; i>= 1; i --) //枚举第i位小于A[i],i之前都等于A[k],i之后不受x限制
{
res += dp[i - 1][2] * A[i]; //i-1位不吉利,i位0~A[i-1]任选
if(flag) res += dp[i - 1][0] * A[i]; //前面的位已经不吉利,这一位随便取
else
{
if(A[i] > 4) res += dp[i - 1][0]; //i位能取到4,吉利数加4不吉利
if(A[i] > 6) res += dp[i - 1][1]; //i位能取到6,下一位是2的情况不吉利
if(A[i + 1] == 6 && A[i] > 2) res += dp[i][1]; //高一位是6,这一位能取到2
}
if(A[i] == 4 || A[i + 1] == 6 && A[i] == 2) flag = true; //前面的位已经不吉利
}
return x - res; //总数-不吉利数 = 吉利数
}
int main()
{
init();
while(1)
{
cin >> n >> m;
if(n == 0 && m == 0) break;
cout << solve(m + 1) - solve(n) << endl;
}
return 0;
}