[CQOI2016]手机号码

题目描述

人们选择手机号码时都希望号码好记、吉利。比如号码中含有几位相邻的相同数字、不含谐音不吉利的数字等。手机运营商在发行新号码时也会考虑这些因素,从号段中选取含有某些特征的号码单独出售。为了便于前期规划,运营商希望开发一个工具来自动统计号段中满足特征的号码数量。

工具需要检测的号码特征有两个:号码中要出现至少 33 个相邻的相同数字;号码中不能同时出现 88 和 44。号码必须同时包含两个特征才满足条件。满足条件的号码例如:13000988721、23333333333、14444101000。而不满足条件的号码例如:1015400080、10010012022。

手机号码一定是 1111 位数,前不含前导的 00。工具接收两个数 LL 和 RR,自动统计出 [L,R][L,R] 区间内所有满足条件的号码数量。LL 和 RR 也是 1111 位的手机号码。

输入格式
输入文件内容只有一行,为空格分隔的 22 个正整数 L,RL,R。

输出格式
输出文件内容只有一行,为 11 个整数,表示满足条件的手机号数量。

输入输出样例
输入 #1复制

12121284000 12121285550
输出 #1复制
5
说明/提示
样例解释:满足条件的号码: 12121285000、 12121285111、 12121285222、 12121285333、 12121285550。

数据范围:10^{10}\leq L\leq R<10^{11}10
10
≤L≤R<10
11。


比较明显的数位dp,不过状态很多。

我们记录上一个数字,上一个数字的上一个数字,当前是否已经满足条件,是否出现4,是否出现8,是否limit,当前是第几位即可。

前导零可以通过枚举第一位来解决,比较方便。


AC代码:

#pragma GCC optimize(2)
#include
#define int long long
using namespace std;
int dp[20][2][10][10][2][2][2],a[20],l,r,flag;
int dfs(int pos,int pre,int st,int ok,int limit,int is4,int is8){
	if(is4&&is8)	return 0;
	if(!pos)	return ok;
	if(dp[pos][ok][pre][st][limit][is4][is8]!=-1)	
		return dp[pos][ok][pre][st][limit][is4][is8];
	int up=limit?a[pos]:9,res=0;
	for(int i=0;i<=up;i++){
		res+=dfs(pos-1,i,pre,ok||i==pre&&pre==st,limit&&i==up,i==4||is4,i==8||is8);
	}
	dp[pos][ok][pre][st][limit][is4][is8]=res;
	return res;
}
int solve(int x){
	if(x<1e10)	return 0;	int res=0;
	int pos=0; memset(dp,-1,sizeof dp);
	while(x)	a[++pos]=x%10,x/=10;
	for(int i=1;i<=a[pos];i++)	res+=dfs(pos-1,i,0,0,i==a[pos],i==4,i==8);
	return res;
}
signed main(){
	cin>>l>>r;
	cout<<solve(r)-solve(l-1)<<endl;
	return 0;
}

你可能感兴趣的:(数位dp)