HDU3555 Bomb 数位DP经典题
来源:互联网 发布:福利软件 编辑:程序博客网 时间:2024/05/22 20:55
传送门:HDU3555
题意:1到n区间内有多少个数字含有‘49’。
题解:典型数位DP的题目,这次做个题的收获是学到了通用的递推解数位DP的方法。
递推解法:
//dp[i][j]:长度为i的数的第j种状态
//dp[i][0]:长度为i但是不包含49的方案数
//dp[i][1]:长度为i且不含49但是以9开头的数字的方案数
//dp[i][2]:长度为i且包含49的方案数
(转)状态转移如下
dp[i][0] = dp[i-1][0] * 10 - dp[i-1][1]; // not include 49 如果不含49且,在前面可以填上0-9 但是要减去dp[i-1][1] 因为4会和9构成49
dp[i][1] = dp[i-1][0]; // not include 49 but starts with 9 这个直接在不含49的数上填个9就行了
dp[i][2] = dp[i-1][2] * 10 + dp[i-1][1]; // include 49 已经含有49的数可以填0-9,或者9开头的填4
接着就是从高位开始统计
在统计到某一位的时候,加上 dp[i-1][2] * digit[i] 是显然对的,因为这一位可以填 0 - (digit[i]-1)
若这一位之前已经有49,那么加上 dp[i-1][0] * digit[i] 也是显然对的。
若这一位之前没有49,但是digit[i]比4大,那么当这一位填4的时候,就得加上dp[i-1][1]
以上转自:http://blog.csdn.net/u012860063/article/details/46820639
递推做的话要预处理出DP数组来,用到的就是上面的三个状态转移方程。
代码:
#include<stdio.h>#include<iostream>#include<string.h>#include<math.h>#include<algorithm>#include<queue>#include<stack>#include<set>#include<vector>#include<map>#define ll long long#define pi acos(-1)#define inf 0x3f3f3f3fusing namespace std;typedef pair<int,int>P;//dp[i][j]:长度为i的数的第j种状态 //dp[i][0]:长度为i但是不包含49的方案数 //dp[i][1]:长度为i且不含49但是以9开头的数字的方案数 //dp[i][2]:长度为i且包含49的方案数 int c[25];ll dp[25][3];void init(){memset(dp,0,sizeof(dp));dp[0][0]=1;for(int i=1;i<=20;i++){dp[i][0]=dp[i-1][0]*10-dp[i-1][1];dp[i][1]=dp[i-1][0];dp[i][2]=dp[i-1][1]+dp[i-1][2]*10;}}ll solve(int len,ll n){ll ans=0;bool flag=false;for(int i=len;i>=1;i--){ans+=dp[i-1][2]*c[i];if(flag)ans+=dp[i-1][0]*c[i];if(!flag&&c[i]>4)ans+=dp[i-1][1];if(c[i+1]==4&&c[i]==9)flag=true;}return ans;}int main(){int t;ll n;init();scanf("%d",&t);while(t--){scanf("%lld",&n);n++;//n++是因为本题要求[1,n]含49的数字的个数,而递推过程求得是[1,n)含49的数字的个数。int len=0;memset(c,0,sizeof(c));while(n){c[++len]=n%10;n/=10;}printf("%lld\n",solve(len,n));}}然后就是经典的记忆化搜索了,我用的是原来从网上扒的一个模板。
代码:
#include<stdio.h>#include<iostream>#include<string.h>#include<math.h>#include<algorithm>#include<queue>#include<stack>#include<set>#include<vector>#include<map>#define ll long long#define pi acos(-1)#define inf 0x3f3f3f3fusing namespace std;typedef pair<int,int>P;// pos = 当前处理的位置(一般从高位到低位)// pre = 上一个位的数字(更高的那一位)// status = 要达到的状态,如果为1则可以认为找到了答案,到时候用来返回,// 给计数器+1。// limit = 是否受限,也即当前处理这位能否随便取值。如567,当前处理6这位,// 如果前面取的是4,则当前这位可以取0-9。如果前面取的5,那么当前// 这位就不能随便取,不然会超出这个数的范围,所以如果前面取5的// 话此时的limit=1,也就是说当前只可以取0-6。//// 用DP数组保存这三个状态是因为往后转移的时候会遇到很多重复的情况。ll DP[25][10][2];int DIG[25];ll dfs(int pos,int pre,int status,int limit){ //已结搜到尽头,返回"是否找到了答案"这个状态。 if(pos < 1) return status; //DP里保存的是完整的,也即不受限的答案,所以如果满足的话,可以直接返回。 if(!limit && DP[pos][pre][status] != -1) return DP[pos][pre][status]; int end = limit ? DIG[pos] : 9; ll ret = 0; //往下搜的状态表示的很巧妙,status用||是因为如果前面找到了答案那么后面 //还有没有答案都无所谓了。而limti用&&是因为只有前面受限、当前受限才能 //推出下一步也受限,比如567,如果是46X的情况,虽然6已经到尽头,但是后面的 //个位仍然可以随便取,因为百位没受限,所以如果个位要受限,那么前面必须是56。 // //这里用"不要49"一题来做例子。 for(int i = 0;i <= end;i ++) ret += dfs(pos - 1,i,status || (pre == 4 && i == 9),limit && (i == end)); //DP里保存完整的、取到尽头的数据 if(!limit) DP[pos][pre][status] = ret; return ret;}int main(){int t;ll n;scanf("%d",&t);while(t--){memset(DP,-1,sizeof(DP));scanf("%lld",&n);int len=0;while(n){DIG[++len]=n%10;n/=10;}printf("%lld\n",dfs(len,0,0,1));}}
注意一下用long long就好了。
- HDU3555 Bomb 数位DP经典题
- 【数位DP】 hdu3555 Bomb
- 【hdu3555】【数位DP】Bomb
- 【数位DP】Bomb HDU3555
- hdu3555 Bomb 数位DP
- HDU3555:Bomb(数位DP)
- hdu3555 Bomb (数位DP)
- HDU3555 Bomb 数位DP
- Hdu3555 - Bomb - 数位dp
- hdu3555 Bomb 数位dp
- hdu3555 Bomb(数位DP)
- HDU3555 Bomb 数位DP
- HDU3555 Bomb 数位DP
- HDU3555 Bomb(数位DP)
- hdu3555 Bomb(数位dp)
- 【数位DP】HDU3555-Bomb
- hdu3555 Bomb --数位dp
- HDU3555 Bomb[数位DP]
- 设备树学习之(九)SPI设备注册过程
- php5.4+ CI框架中无法写日志
- git搭建局域网服务器
- 如何用github+hexo搭建个人博客
- Redis String、List、Set、Hash、ZSet常用命令
- HDU3555 Bomb 数位DP经典题
- vue 动画过渡
- 算法提高 输入输出格式练习
- 在Eclipse中显示.project和.classpath和.setting目录
- SPOJ_LEXSTR:Lexicographically_Smallest(并查集)
- 模拟Spring的AOP
- HDU Moving Tables
- 趣味百题之逻辑推理
- java中的instanceof