题目链接:
http://acm.hdu.edu.cn/showproblem.php?pid=2089
题解:
开一个dp数组,设i代表当前数字的长度,dp[i][0]表示不含4和62的数字, dp[i][1] 表示不含4和62且最高位为2的数字,dp[i][2]表示含有4和62的数字。可以推出以下的递推公式:
dp[0][0] = 1;
for(i = 1; i<=6; i++)//数字最长为6
{
dp[i][0] = dp[i-1][0]*9-dp[i-1][1];//最高位加上不含4的9个数字的状况,但因为会放6,所以要减去前一种开头为2的情况
dp[i][1] = dp[i-1][0];//开头只放了2
dp[i][2] = dp[i-1][2]*10+dp[i-1][0]+dp[i-1][1];//已经含有的前面放什么数都可以,或者是放一个4,或者是在2前面放6
}
然后按位读入数字,从最高位开始读数字判断,设flag为之前的高位是否出现过4或者62,ans为不吉利数字的数量。
开始先将第i位数字与前一位含有4和62的数字的乘积加入ans(用1到74举例,当遍历到7时加入的是14,24,34,44,54,64,74,遍历到4时加入的数字是4),然后分情况讨论:
如果flag为真,说明已经是不吉利数字了,那么接下来怎么处理已经无所谓了,所以加上该位数字与前一位数字不含4和62的数字的乘积。
如果flag不为真且当前位数字大于4,代表可以将以4开头的数字加入ans,所以ans+=number[i] * dp[i-1][0](还是1到74为例,遍历到7时加入的数字是40 ,41,42,43,45,46,47,48,49)
如果flag不为真且当前位数字大于6,代表可以加入62(以1到74为例,此时加入了62)
如果flag不为真且后一位数字等于6且当前位数字大于2,代表可以加入62(加入62的第二种情况)
总结:按照1到74的例子来说,遍历结束后ans为18,74 - 18即是我们需要的值。
AC代码:
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
int n, m;
int dp[7][3];
int solve(int number)//处理的数字不包含number本身
{
int numb[8] = {0}, len = 0, ans = 0,cnt = number;
while(number != 0)
{
numb[++len] = number % 10;
number /= 10;
}
bool flag = false;
for(int i = len; i > 0; i--)
{
ans += numb[i] * dp[i-1][2];
if(flag)
ans += numb[i] * dp[i-1][0];
if(!flag && numb[i] > 4)
ans += dp[i-1][0];
if(!flag && numb[i+1] == 6 && numb[i] > 2)
ans += dp[i][1];
if(!flag && numb[i] > 6)
ans += dp[i-1][1];
if((numb[i] == 2 && numb[i+1] == 6)|| numb[i] == 4)
flag = true;
}
return cnt - ans;
}
int main()
{
dp[0][0] = 1;
for(int i = 1; i < 7; i++)
{
dp[i][0] = dp[i-1][0] * 9 - dp[i-1][1];
dp[i][1] = dp[i-1][0];
dp[i][2] = dp[i-1][0] + dp[i-1][1] + dp[i-1][2] * 10;
}
while(cin >> n >> m)
{
if(n ==0 && m == 0)break;
cout << solve(m + 1) - solve(n) <<endl;
}
return 0;
}