HDU 2089 不要62(数位dp) 超级详细!!!

不要62
Time Limit: 1000/1000 MS (Java/Others)
Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 76677 Accepted Submission(s): 31213

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

题目意思:

给出一个区间,统计一个区间的数,该数满足2个条件,第一就是不能有4,第二就是不能有连续62出现。

例如:162存在连续62,那么162不算。456存在4,那么456也不算。

思路分析:
传统暴力的方式,对于区间[n,m] ,我们暴力统计该数字子是否存在4或者62即可。对于数据量较小的,那么我们可以暴力统计,求前缀和。
次题数据范围较大,我们采用数位dp来解决。

下面先给出java版本的暴力(目的是为了检测我的dp是不是对的,说实话我dbug都是用它的,哈哈


import java.util.Scanner;

public class Main
{
	public static void main(String[] args) 
	{
		
		Scanner sc=new Scanner(System.in);
		while(sc.hasNext())
		{
			int n=sc.nextInt();
			int m=sc.nextInt();
			long c=0;
			for(int i=n;i<=m;i++)
			{
				if(pd(i))c++;
			}
			System.out.println(c);
		}
		
	}
	public static boolean pd(int n)
	{
		String st=String.valueOf(n);
		if(st.contains("4")||st.contains("62"))return false;
		else return true;
	}
}
	

好了,暴力就这样,数位dp才是重点讲解!!!!

1、首先,我们这样思考,处理4比处理62要容易,我们先考虑如果把存在4的数都筛掉。

2、我们考虑1位数2位数的情况

下面表格列出
在这里插入图片描述
3、考虑了一位数和二位数的,那么我们尝试考虑三位数

例如 三位数,以1开头的,有哪些????

其实,三位数1开头,就是二位数满足条件的总数=9+9+9+9+9+8+9+9+9

4、考虑dp方程的构造!

dp[i][j] i 表示位数,j 表示以什么数字开始.

例如 dp[2][6] 代表2位数,以数字6开始的满足条件的个数,查找上表,发现是8个

dp[3][1] =dp[2][1]+dp[2][2]+dp[2][3]+dp[2][4]+dp[2][5]+dp[2][6]+dp[2][7]+dp[2][8]+dp[2][9] 这就是的展开。

所以很容易得出dp方程
在这里插入图片描述
题目说不存在4,那么只要j不等于4,那么所有以4开头的数字都被筛掉了。(完美)

//这是处理4的dp
for(int i=0;i<10;i++)
	{
		if(i==4)continue;
		dp[1][i]=1;
	}
	for(int i=1;i<=8;i++)//枚举i位,题目最大数是7位,我这里开了8位保险起见
	{
		for(int j=0;j<10;j++)//i位,j开头的数字
		{
			for(int k=0;k<10;k++)//把前一个状态的0-9所有满足条件的加起来
			{
				if(j!=4)//筛选掉4 
				{ 
					dp[i][j]+=dp[i-1][k]; //dp方程
				}
			}
		}
	}

4处理好了,想必62也很简单,如果以6开始,那么我们后面如果出现2,都不要。

所以代码就一句if

if(j==6&&k==2)continue;//如果6打头,后面跟2就筛选掉 

现在dp处理完了,大功告成,但是题目要求的是区间个数。

首先需要知道,区间【l,r】的答案,就是 【0,r】的答案减去 【0,l-1】的答案。

现在我们知道,emmm比如,一个三位数,开头是1的,dp[3][1]我们能很快拿到。

如果给出一个数, 364。 我们要怎么求区间【1,364】之间的所以满足条件的数呢

我们从最高位开始看,先看3
3代表的是300,可以拆成0-100,100-200,200-300
那么,dp[3][0]+dp[3][1]+dp[3][2] (说明一下这里的dp[3][0]并不是无意义的,他统计了前面2位数的所有满足条件的个数,不清楚的可以打印出来看看)。

下面看代码如果实现:

	int digit[10];//存每一个数字
	int cnt=0;//计数变量
	while(x>0)
	{
		digit[cnt++]=x%10;
		x/=10;
	}
	int ans=0;//答案变量
	for(int i=cnt-1;i>=0;i--)//由于存的是反序,高位到最后去了,所以我们倒着循环
	{
		for(int j=0;j<digit[i];j++)//例如326 ,第一次处理3,j分别取0,1,2
		{//ans=ans+dp[3][0]+dp[3][1]+dp[3][2]
			if(j!=4)
			{//无时不刻要知道,后面不能接4
				ans+=dp[i+1][j];
			}
		}
		if(digit[i]==4)//如果当前为是4,那么后面不需要考虑了
		{
			ans--;
			break;
		}
	}

最后我们处理62,思路:如果当前以2开始,我们看他前一位是不是6,如果是6就pass。注意不要越界,本弱因越界wa2次

如果当前为是6,后一位是2,那么后面的数也不用看了,
举个例子,462999 这个数字62后面的999都是无效的,他们等价于462000

下面是AC代码:

#include <bits/stdc++.h>

using namespace std;

int dp[10][10];

int n,m; 

void solve()
{
	for(int i=0;i<10;i++)
	{
		if(i==4)continue;
		dp[1][i]=1;
	}
	for(int i=1;i<=8;i++)
	{
		for(int j=0;j<10;j++)
		{
			for(int k=0;k<10;k++)
			{
				if(j!=4)//筛选掉4 
				{
					if(j==6&&k==2)continue;//如果6打头,后面跟2就筛选掉 
					dp[i][j]+=dp[i-1][k];
				}
			}
		}
	}
}

int cal(int x)
{
	int digit[10];
	int cnt=0;
	while(x>0)
	{
		digit[cnt++]=x%10;
		x/=10;
	}
	int ans=0;
	for(int i=cnt-1;i>=0;i--)
	{
		for(int j=0;j<digit[i];j++)
		{
			if(j!=4)
			{
				if(i!=cnt-1&&j==2&&digit[i+1]==6)continue;//注意不要越界
				ans+=dp[i+1][j];
			}
		}
		if(digit[i]==4)
		{
			ans--;
			break;
		}
		if(i!=cnt-1&&digit[i]==2&&digit[i+1]==6)//注意不要越界
		{
			ans--;
			break;
		}
	}
	return ans;
}

int main()
{
	solve();
	while(~scanf("%d%d",&n,&m))
	{
		if(n==0&&m==0)break;
		printf("%d\n",cal(m)-cal(n-1));
	}
	return 0;
} 

最后给出一组数据:

输入:
1 3
1 4
1 14
1 1000000
156 1969
111 11111
123 564
62 6262
622 62262624
444 555444
996 1314
1 1
55 55
44 44
62 62
1 62

输出:
3
3
12
499121
1274
7031
267
3668
23177469
252760
257
1
1
0
0
46

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值