不要62
Time Limit: 1000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 57271 Accepted Submission(s): 22409
Problem Description
杭州人称那些傻乎乎粘嗒嗒的人为62(音:laoer)。
杭州交通管理局经常会扩充一些的士车牌照,新近出来一个好消息,以后上牌照,不再含有不吉利的数字了,这样一来,就可以消除个别的士司机和乘客的心理障碍,更安全地服务大众。
不吉利的数字为所有含有4或62的号码。例如:
62315 73418 88914
都属于不吉利号码。但是,61152虽然含有6和2,但不是62连号,所以不属于不吉利数字之列。
你的任务是,对于每次给出的一个牌照区间号,推断出交管局今次又要实际上给多少辆新的士车上牌照了。
Input
输入的都是整数对n、m(0<n≤m<1000000),如果遇到都是0的整数对,则输入结束。
Output
对于每个整数对,输出一个不含有不吉利数字的统计个数,该数值占一行位置。
Sample Input
1 100 0 0
Sample Output
80
Author
qianneng
Source
问题链接:HDU2089 不要62
问题简述:(略)
问题分析:
这是一个数位DP问题。
数位DP一般应用于求出在给定区间[A,B]内,符合条件P(i)的数i的个数,条件P(i)一般与数的大小无关,而与数的组成有关。
实际计算时,可以采用记忆化搜索实现,已经搜索过的就不再搜索。这种计算也可以称为记忆化计算,已经计算过的就不再计算,可以避免重复的计算,加快计算速度。
数位DP可以采用直接搜索和记忆化搜索两种方式来处理。
程序说明:
记忆化搜索:
设计数组dp[][]是一个关键!不同的问题略有不同,有经验后就简单了。
一种定义是dp[i][0 / 1] 表示不含 4 和 62的前提下,剩余长度为i,首位是否为 6 的个数。初值为-1。
另外一种定义是dp[i][d]表示共i位,前导为数字d的满足条件(不含62和4)数的数量。
不同的数组dp[][]定义,使得程序的逻辑不一样。采用后一种定义,程序逻辑要简单许多。
数组dp[][]的元素初始化为-1,表示其值尚未计算得到,需要用函数dfs()进行计算。初始化应该放在主函数中循环处理之前进行,可以最大限度避免重复计算。
函数solve(n)的功能是计算(0,n]的满足条件的数的个数。做法是将n的各位分解成数字位0-9,放入数组digits[]中,个位放在digits[0]中,即低位放在下标小的数组元素中,高位放在下标大的数组元素中。然后通过深度优先搜索函数dfs(),根据数组digits[]指定的数去搜索。
有关limit变量,以n=5676为例,简单说明如下:
1.开始时从最高位开始搜索,即从千位5开始,可取的数字只能是0-5(首次调用函数limit的实参是1,即只能取0-5);
2.千位若取0-4,百位可取的值则为0-9;
3.千位若取5,百位可取的值就只能取1-6(6是n的百位数字);
4.根据前2条,就用参数limit来控制下一个数位的取值范围,在搜索的时候就看是否i==digits[pos],不等的话limit取值false,下一位(低一位)取值范围则为0-9,否则limit取值true,下一位(低一位)取值范围则为0-x,x为下一位的数字。
题记:(略)
参考链接:(略)
AC的C++语言程序(数位DP+记忆化搜索,简洁易懂)如下:
/* HDU2089 不要62 */
#include <bits/stdc++.h>
using namespace std;
const int N = 20; // 位数,int类型不超过10位
const int D = 10; // 10进制数字的个数
int digits[N + 1];
int dp[N][D]; // dp[i][d]-共i位,前导为数字d的满足条件(不含62和4)数的数量
/*
* 参数:
* pos - 数位位置,即当前处理数的第几位,从高位开始
* pre - 前导,即前一位数字
* limit - 是否为数位上界(最大数字)
*/
int dfs(int pos, int pre, bool limit)
{
if(pos == -1) // 递归边界,已经枚举结束,则1个数满足条件
return 1;
if(!limit && dp[pos][pre] != -1) // 已经搜索过的不再搜索,直接使用之前的计算结果
return dp[pos][pre];
// 计数
int ans = 0;
int maxd = limit ? digits[pos] : 9; // 枚举数字,如果数字不同则枚举0-9
for(int i = 0; i <= maxd; i++) {
if(i == 4 || (pre == 6 && i == 2))
;
else
ans += dfs(pos - 1, i, limit && i == digits[pos]);
}
if(!limit)
dp[pos][pre] = ans;
return ans;
}
// 计算[0,n]中不含62和4数的数量
int solve(int n)
{
int len = 0;
while(n) {
digits[len++] = n % 10;
n /= 10;
}
return dfs(len - 1, 0, 1);
}
int main()
{
memset(dp, -1, sizeof(dp));
int n, m;
while(~scanf("%d%d", &n, &m) && (n || m))
printf("%d\n", solve(m) - solve(n - 1));
return 0;
}
AC的C++语言程序(数位DP+记忆化搜索)如下:
/* HDU2089 不要62 */
#include <bits/stdc++.h>
using namespace std;
const int N = 10;
int dp[N][2], digits[N + 1];
/*
* 参数:
* pos - 数位位置,即当前处理数的第几位,从高位开始
* pre - 前导,即前一位数字
* state - 状态,dp[i][state]表示剩余长度为i,首位是否包含指定数字(6)
* limit - 数位上界,即最大数字
*/
int dfs(int pos, int pre, int state, bool limit)
{
if(pos == -1) // 递归边界,已经枚举结束,这个数是合法的,1个
return 1;
if(!limit && dp[pos][state] != -1) // 已经搜索过的不再搜索,直接使用之前的计算结果
return dp[pos][state];
// 计数
int ans = 0;
int maxd = limit ? digits[pos] : 9; // 枚举数字,如果数字不同则枚举0-9
for(int i = 0; i <= maxd; i++) {
if(i == 4 || (pre == 6 && i == 2))
continue;
ans += dfs(pos - 1, i, i == 6, limit && i == digits[pos]);
}
if(!limit)
dp[pos][state] = ans;
return ans;
}
int solve(int n)
{
int len = 0;
while(n) {
digits[len++] = n % 10;
n /= 10;
}
return dfs(len - 1, - 1, 0, 1);
}
int main()
{
int n, m;
memset(dp, -1, sizeof(dp));
while(~scanf("%d%d", &n, &m) && (n || m))
printf("%d\n", solve(m) - solve(n -1));
return 0;
}
AC的C++语言程序(数位DP)如下:
/* HDU2089 不要62 */
#include <iostream>
#include <stdio.h>
using namespace std;
const int M = 6;
const int N = 10;
int dp[N][N], digits[N + 1]; // dp[i][j]表示i位数,其中首位为数字j的数量
void init()
{
dp[0][0] = 1;
for (int i = 1; i <= M; i++)
for (int j = 0; j < N; j++)
for (int k = 0; k < N; k++)
if (j != 4 && !(j == 6 && k == 2))
dp[i][j] += dp[i - 1][k];
}
int solve(int n)
{
int len = 0;
while(n) {
digits[++len] = n % 10;
n /= 10;
}
digits[len + 1] = 0;
int ans = 0;
for(int i = len; i >= 1; i--) {
for(int j = 0; j < digits[i]; j++) {
if(digits[i + 1] != 6 || j != 2)
ans += dp[i][j];
}
if(digits[i] == 4 || (digits[i + 1] == 6 && digits[i] == 2))
break;
}
return ans;
}
int main()
{
init();
int n, m;
while(~scanf("%d%d", &n, &m) && (n || m))
printf("%d\n", solve(m + 1) - solve(n));
return 0;
}