hdu 不要62 数位dp

不要62

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

题意:

给你两个数作为一个闭区间的端点,求出该区间中不包含数字4和62的数的个数

dp[i][j],i表示第i位,j表示第i位是j且数字中符合不包含4和62的个数

solve函数功能是将[0,n]区间内不包含数字4和62的数的个数求出

最后将两个solve相减,即可求出个数

#include<iostream>
#include<cstring>
using namespace std;
#define Max 10000

typedef long long ll;
long long dp[Max][10];
int a[100];
 
void getdp(){  //准备工作
	memset(dp,0,sizeof(dp));  
	dp[0][0]=1;

	for(int i=1;i<=9;i++)  
		for(int j=0;j<=9;j++)
			for(int k=0;k<=9;k++)	//注意这种dp的初始化方式,我本人还是比较喜欢的!!!
				if(j!=4 && !(j==6&&k==2))
					dp[i][j]+=dp[i-1][k];
			
//dp[i][j],i表示第i位,j表示第i位的数字为j!!!
}
ll solve(int x){ //分解工作
	int len=0;
	while(x>0){
		a[++len]=x%10;
		x/=10;
	}	
	a[len+1]=0;//注意前置0这一手处理得很好!!!相当nice
	ll ans=0;  //前置0这一手为 !( a[i+1]==6 && j==2 ) )做了铺垫!!!
	for(int i=len;i;i--)
	{
		for(int j=0;j<a[i];j++)
			if(j!=4 && !( a[i+1]==6 && j==2 ) )
				ans+=dp[i][j];
			
		if(a[i]==4 || (a[i]==2 && a[i+1]==6))
			break;
	}
	return ans;
}
int main(){
	getdp();
	ll left,right;
	while(cin>>left>>right)
	{
		if(left==right && left==0) break;
		printf("%lld\n",solve(right+1)-solve(left));		
	}
} 

数位动态规划 
数位动态规划是求解一个大区间[L, R]中间满足条件Q的所有数字的个数(或者和,或其他)的一种方法。

它通过分析每一位上的数字,一般用 dp[len][digit][...] 来表示状态“len位长的数字,最高位数字为digit所具有的xx特性”,利用记忆化搜索保存中间结果,从而加快求解速度。    

通过求 f(n) 从0到n中满足条件Q的数字的个数,则所求的结果为 f(R) - f(L-1). 
大多数数位dp都可以用一个DFS函数来进行记忆化搜索:

 

板子:

//len数字的位数,digit最高位的值,end_flag 表示digit是否是第len位(从低位向高位数,个位为第1位) 的范围边界
int Dfs(int len, int digit, bool end_flag){
    //超出边界
    if (len <= 0 || digit > 9 || digit < 0)
        return 0;
    //记忆化搜索,如果之前已经求出来了,则返回。注意这里要求 end_flag为false
    if (!end_flag && dp[len][digit] != -1)
        return dp[len][digit];
 
    // 最简单情况看数字是否满足要求
    if (len == 1)
        return dp[len][digit] = xxx;
     
    //如果当前位是边界数字N对应位的最大值,则下一位的范围只能从0到边界数字N的下一位的最大值。否则为0 到 9
    int end = end_flag ? bits[len - 2] : 9;
 
    int ans = 0;
    for (int i = 0; i <= end; i++){
        ans += Dfs(len - 1, i, end_flag && (i==end));
    }
    if (!end_flag) //digit不是第len位的最高范围,则可以将结果缓存
        dp[len][digit] = ans;
    return ans;
}

题目大意 
    给定一个区间[L, R],求区间内满足条件“数位上不含有4,且不含有62(62必须连续)”的数字的个数。

分析 
    直接套用模板

实现

#include<iostream>
#include<stdio.h>
using namespace std;
int dp[9][10];
int bits[8];
 
//用dfs进行记忆化搜索, dp[len][digit] 表示 len位数字,最高位为digit满足条件的个数. 这里对数字范围没有限制!
//搜索的时候,若要进行记忆化,需要 dp[len][digit]的结果对数字范围没有限制,因此需要判断 end_flag来决定是否进行记忆。
 
 
//len数字的位数,digit最高位的值,end_flag 表示digit是否是第len位(从低位向高位数,个位为第1位) 的范围边界
int Dfs(int len, int digit, bool end_flag){
    //超出边界
    if (len <= 0 || digit > 9 || digit < 0)
        return 0;
    //记忆化搜索,如果之前已经求出来了,则返回。注意这里要求 end_flag为false
    if (!end_flag && dp[len][digit] != -1)
        return dp[len][digit];
 
    // 最简单情况看数字是否满足要求
    if (len == 1)
        return dp[len][digit] = (digit != 4);
    if (digit == 4)
        return dp[len][digit] = 0;
     
    //如果当前位是边界数字N对应位的最大值,则下一位的范围只能从0到边界数字N的下一位的最大值。否则为0 到 9
    int end = end_flag ? bits[len - 2] : 9;
 
    int ans = 0;
    for (int i = 0; i <= end; i++){
        if (!(digit == 6 && i == 2)) //除去 62连续的情况
            ans += Dfs(len - 1, i, end_flag && (i==end));
    }
    if (!end_flag) //digit不是第len位的最高范围,则可以将结果缓存
        dp[len][digit] = ans;
    return ans;
}
 
//将数字n的各个位上的范围求出来,保存到bits数组中,返回数字n的长度
int Init(int n){   
    memset(bits, 0, sizeof(bits));
    int k = 0;
    while (n){
        bits[k++] = n % 10;
        n /= 10;
    }
    return k;
}
 
int Solve(int n){  
    int len = Init(n); 
    //数字长度为len,则为了避免首位遍历从0到bits[len-1],给数字增加一个前导0。
    //len + 1位,首位为0,且0是最高位的边界
    int ans = Dfs(len + 1, 0, true);
    return ans;
}
int main(){
    int m, n;
    memset(dp, -1, sizeof(dp));
    while (scanf("%d %d", &m, &n)){
        if (m == 0 && n == 0)
            break;             
        int ret1 = Solve(m-1);
        int ret2 = Solve(n);
        int ret = ret2 - ret1;
        printf("%d\n", ret);
    }
    return 0;
}

 

 

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值