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