【ACM】不要62 (数位DP)
题目:http://acm.acmcoder.com/showproblem.php?pid=2089
杭州人称那些傻乎乎粘嗒嗒的人为62(音:laoer)。
杭州交通管理局经常会扩充一些的士车牌照,新近出来一个好消息,以后上牌照,不再含有不吉利的数字了,这样一来,就可以消除个别的士司机和乘客的心理障碍,更安全地服务大众。
不吉利的数字为所有含有4或62的号码。例如:
62315 73418 88914
都属于不吉利号码。但是,61152虽然含有6和2,但不是62连号,所以不属于不吉利数字之列。
你的任务是,对于每次给出的一个牌照区间号,推断出交管局今次又要实际上给多少辆新的士车上牌照了。
思路:写了一个蛮力算法,直接超时了。之后各种想不出来,上网搜答案。结果发现有专门的解法,叫数位DP。之后看答案看了2个小时,那50行代码翻来覆去看了好久,终于看明白了。
唉,大神们写代码的时候注释都太精简了,像我这种没学过数位DP的看得很痛苦啊。
下面解析一下:
题目会给出两个数字 m 和 n,我们要找到 【m, n】区间内,不含4与62的数字的个数。
①我们把问题拆解为两个部分, 分别求0 ~ m - 1 和 0 ~ n 之间的不含4与62的数字的个数,然后相减。
②但是0~n中的不含4与62的值求解也很复杂,所以我们先进一步化简,求0到 i 位数的不含4和62的数字个数。
比如:
i = 1,即求 0 ~ 9 中不含4和62的数字个数
i = 2,即求 0 ~ 99 中不含4和62的数字的个数
i = 3,即求 0 ~ 999 中不含4和62的数字个数
i = 4,即求 0 ~ 9999 中不含4和62的数字的个数
..... 以此类推
用dp[i][0] 来存储 0 到 i 位数字中不含4和62的数字个数,即幸运数
用dp[i][1] 来存储 0 到 i 位数字中以 2 开头的幸运数。
用dp[i][2] 来存储 0 到 i 位数字中的非幸运数,即包含4或者62的数字。
那么,可以用下面的递推公式
dp[i][0] = dp[i - 1][0] * 9 - dp[i - 1][1] // i 位数字中的幸运数个数 = (i - 1)位幸运数字前面加上0 - 9 中除去4以外的9个数字 - 以2开头的(i - 1)位幸运数字前面加上了6.
dp[i][1] = dp[i - 1][0] // 0到 i 位数字中以2开头的幸运数 = 0到 i 位数字中所有的幸运数字前面加上2
dp[i][2] = dp[i - 1][2] * 10 + dp[i - 1][0] + dp[i - 1][1] //0到 i 位的非吉利数 = 0到 i - 1 位的非吉利数前面加上0-9的任何数字 + i-1位的吉利数字前面加上了4 + i-1位以2开头的吉利数字前面加上了6.
初始值: dp[0][0] = 1 dp[0][1] = dp[0][2] = 0;
根据初始值和递推公式,我们就能得到从0到任意i位数字的吉利数字的个数。
③找到0 ~ n 的吉利数字的个数
我们先求出0 ~ n 之间非吉利数字的个数,用总数减去即可。那,非吉利数字的个数怎么求呢?
用具体的数字举例来说吧:设 n = 583626
用digit[10]记录n+1每一位对应的数字,此例中有6位数字(令cnt = 6 表示数字位数),分别是
digit[6] = 5
digit[5] = 8
digit[4] = 3
digit[3] = 6
digit[2] = 2
digit[1] = 7
digit[0] = 任意数字,占位用的
用sum记录非吉利数字的个数,初始化为0
需要一个bool量 flag,记录是否出现了非吉利数字。初始化为false, 未出现。
我们从数字的最高位起进行判断:digit[6] = 5, 我们求 0 ~ 499999 之间非吉利数的个数。
首先:加上0 ~ 99999中所有非吉利数字前面添加0~4的任意一个数字的情况 sum += dp[5][2] * digit[6]
其次:5大于4,故我们要加上 0~99999中所有吉利数字前面添加4的情况 sum += dp[5][0]
接着,判断第5位digit[5] = 8,即判断500000 ~ 579999 之间的非吉利数字的个数,其实就是判断0 ~ 79999之间的,前面的数字不是6就没有什么用
首先:加上0 ~ 9999中所有非吉利数字前面添加0~7的任意一个数字的情况 sum += dp[4][2] * digit[5]
其次:8大于4,故我们要加上 0~9999中所有吉利数字前面添加4的情况 sum += dp[4][0]
此外:8大于6,故我们要加上0~9999中所有以2开头的吉利数字前添加6的情况 sum += dp[4][1]
接着,判断第4位digit[4] = 3,即判断580000 ~ 582999 之间的非吉利数字的个数,其实就是判断0 ~ 2999之间的
首先:加上0 ~ 999中所有非吉利数字前面添加0~2的任意一个数字的情况 sum += dp[3][2] * digit[4]
其次:2小于4,没有需要特别考虑的
此外:2小于6,没有需要特别考虑的
接着,判断第3位digit[3] = 6,即判断583000 ~ 583599 之间的非吉利数字的个数,其实就是判断0 ~ 599之间的
首先:加上0 ~ 99中所有非吉利数字前面添加0~5的任意一个数字的情况 sum += dp[2][2] * digit[3]
其次:6大于4,故我们要加上 0~99中所有吉利数字前面添加4的情况 sum += dp[2][0]
接着,判断第2位digit[2] = 2,即判断583600 ~ 583619 之间的非吉利数字的个数,其实就是判断0 ~ 19之间的,
首先:加上0 ~ 9中所有非吉利数字前面添加0~1的任意一个数字的情况 sum += dp[1][2] * digit[2]
其次:2小于4,没有需要特别考虑的
此外:2小于6,没有需要特别考虑的
但是,需要注意的是,这里判断的数字出现了62,我们要把flag标识为true。
最后,判断第1位digit[1] = 7, 判断583620 ~ 583626但是这里flag为true了,表示前面的数字里面已经包含了非吉利数字,所以后面需要把所有的数字情况都加入到非吉利里面。(正是因为每次判断的数字末尾都比该位的数字少1,所以最开始要记录n + 1 的值)
sum += digit[1] * dp[0][2] + digit[1] * dp[0][0]
总结一下,这部分的算法如下:
int flag=,ans=;
for(int i=cnt;i>;i--){
ans+=digit[i]*dp[i-][]; //由上位所有非吉利数推导
if(flag) //之前出现非吉利的数字
ans+=digit[i]*dp[i-][];
else{
if(digit[i]>) //出现4
ans+=dp[i-][];
if(digit[i]>) //出现6
ans+=dp[i-][];
if(digit[i+]== && digit[i]>) //出现62
ans+=dp[i][];
}
if(digit[i]== || (digit[i+]== && digit[i]==))
flag=;
}
整体的代码如下:
#include<iostream>
#include<cstdio>
#include<cstring> using namespace std; int dp[][]; void Init(){ //预处理,算出所有可能
memset(dp,,sizeof(dp));
dp[][]=;
for(int i=;i<=;i++){
dp[i][]=dp[i-][]*-dp[i-][]; //在不含不吉利数62和4的首位分别补除了4的9个数字,减去在2前面补6的个数
dp[i][]=dp[i-][]; //在不含不吉利数在首位补2
dp[i][]=dp[i-][]*+dp[i-][]+dp[i-][]; //各种出现不吉利数的情况
}
} int Solve(int x){
int digit[];
int cnt=,tmp=x;
while(tmp){
digit[++cnt]=tmp%;
tmp/=;
}
digit[cnt+]=;
int flag=,ans=;
for(int i=cnt;i>;i--){
ans+=digit[i]*dp[i-][]; //由上位所有非吉利数推导
if(flag) //之前出现非吉利的数字
ans+=digit[i]*dp[i-][];
else{
if(digit[i]>) //出现4
ans+=dp[i-][];
if(digit[i]>) //出现6
ans+=dp[i-][];
if(digit[i+]== && digit[i]>) //出现62
ans+=dp[i][];
}
if(digit[i]== || (digit[i+]== && digit[i]==))
flag=;
}
return x-ans; //所有的数减去非吉利的数
} int main(){
int a,b;
Init();
while(~scanf("%d%d",&a,&b)){
if(a== && b==)
break;
printf("%d\n",Solve(b+)-Solve(a));
}
return ;
}
网上有更简洁的代码,用dfs和状态转移做的,我没看懂。
http://blog.csdn.net/dgq8211/article/details/9296953
#include <stdio.h>
#include <string.h>
#include <algorithm> using namespace std; int dp[][],digit[]; int dfs(int len,bool state,bool fp)
{
if(!len)
return ;
if(!fp && dp[len][state] != -)
return dp[len][state];
int ret = , fpmax = fp ? digit[len] : ;
for(int i=;i<=fpmax;i++)
{
if(i == || state && i == )
continue;
ret += dfs(len-,i == ,fp && i == fpmax);
}
if(!fp)
dp[len][state] = ret;
return ret;
} int f(int n)
{
int len = ;
while(n)
{
digit[++len] = n % ;
n /= ;
}
return dfs(len,false,true);
} int main()
{
int a,b;
memset(dp,-,sizeof(dp));
while(scanf("%d%d",&a,&b),a||b)
{
printf("%d\n",f(b)-f(a-));
}
return ;
}
第三种可以通过的方法是暴力打表,这个比较简单
http://www.cnblogs.com/zqxLonely/p/4092259.html
#include <stdio.h>
#include <string.h> int flag[]; int main(){
int n;
int m;
int i;
int temp;
int amount; memset(flag,,sizeof(int)*);
for(i=;i<=;i++){
temp=i;
while(temp){
if(temp%== || temp%==){
flag[i]=;
break;
}
temp/=;
}
} while(){
scanf("%d%d",&n,&m); if(n== && m==)
break; amount=;
for(i=n;i<=m;i++){
if(flag[i]==)
amount++;
} printf("%d\n",m-n+-amount);
} return ;
}
【ACM】不要62 (数位DP)的更多相关文章
- HDU 2089 - 不要62 - [数位DP][入门题]
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=2089 Time Limit: 1000/1000 MS (Java/Others) Memory Li ...
- [hdu 2089] 不要62 数位dp|dfs 入门
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=2089 题意:求[n, m]区间内不含4和62的数字个数. 这题有两种思路,直接数位dp和dfs 数位d ...
- HDU2089 不要62 —— 数位DP
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=2089 不要62 Time Limit: 1000/1000 MS (Java/Others) M ...
- HDU 2089 不要62 数位DP模板题
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=2089 参考博客:https://www.cnblogs.com/HDUjackyan/p/914215 ...
- HDU2089 不要62[数位DP]
不要62 Time Limit: 1000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)Total Submis ...
- 【Hdu2089】不要62(数位DP)
Description 题目大意:给定区间[n,m],求在n到m中没有"62"或"4"的数的个数. 如62315包含62,88914包含4,这两个数都是不合法的 ...
- HDU 2089 不要62(数位DP·记忆化搜索)
题意 中文 最基础的数位DP 这题好像也能够直接暴力来做 令dp[i][j]表示以 j 开头的 i 位数有多少个满足条件 那么非常easy有状态转移方程 dp[i][j] = sum{ dp[ ...
- Hdu 2089 不要62 (数位dp入门题目)
题目链接: Hdu 2089 不要62 题目描述: 给一个区间 [L, R] ,问区间内不含有4和62的数字有多少个? 解题思路: 以前也做过这个题目,但是空间复杂度是n.如果数据范围太大就GG了.今 ...
- hdu2089不要62(数位dp)
不要62 Time Limit: 1000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)Total Submis ...
- 不要62(数位DP)
不要62 http://acm.hdu.edu.cn/showproblem.php?pid=2089 Time Limit: 1000/1000 MS (Java/Others) Memory ...
随机推荐
- 4 Django简介
MVC与MTV模型 MVC Web服务器开发领域里著名的MVC模式,所谓MVC就是把Web应用分为模型(M),控制器(C)和视图(V)三层,他们之间以一种插件式的.松耦合的方式连接在一起,模型负责业务 ...
- BZOJ 5004: 开锁魔法II
比较显然 #include<cstdio> #include<algorithm> #include<cstring> using namespace std; i ...
- Still unable to dial persistent://blog.csdn.net:80 after 3 attempts
动不动电脑有些网站打不开了,还报错: Still unable to dial persistent://blog.csdn.net:80 after 3 attempts 为什么呢? 是dns坏了? ...
- 常见算法用Pascal实现
基本算法 这些都是非常基本的的算法,希望所有学习的人都能理解! 1.数论算法 求两数的最大公约数 function gcd(a,b:integer):integ ...
- 《算法》C++代码 SPFA
SPFA的全称是Shortest Path Faster Algorithm,一看名称八成就是中国人起的名字,因为外国人起算法名称一般都会写上自己的名字,很少谦虚.实际上,这是西南交通大学段凡丁同学于 ...
- jmeter 运行脚本报错 java.net.BindException: Address already in use
在win下跑jmeter时,在聚合报告中出现错误.打开日志文件(前提是将日志写入了指定文件) 发现报错的原因为:java.net.BindException: Address already in u ...
- appium-手势密码实现-automationName 是automator2
上一篇博客已经说了 appium-手势密码实现-automationName 是Appium的情况 下面就说一下automator2的情况: 手势密码的moveTo方法的参数进行了改变. 参数是相对于 ...
- python 学习分享-实战篇类 Fabric 主机管理程序开发
# 类 Fabric 主机管理程序开发: # 1. 运行程序列出主机组或者主机列表 # 2. 选择指定主机或主机组 # 3. 选择让主机或者主机组执行命令或者向其传输文件(上传/下载) # 4. 充分 ...
- 编译TypeScript(TypeScript转JavaScript)
1.配置tsconfig.json文件 tsconfig.json文件配置说明 { "compilerOptions": { //生成相关说明,TypeScript编译器如何编译. ...
- 1155 Heap Paths (30 分)(堆+dfs遍历)
比较简单的一题 遍历左右的时候注意一下 #include<bits/stdc++.h> using namespace std; ; ]; ; vector<int>t; ve ...