【数位dp】【CQOI2016】手机号码

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

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

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

【输入描述】
输入文件内容只有一行,为空格分隔的两个正整数 L,R

【输出描述】
输出文件内容只有一行,为一个整数,表示满足条件的手机号数量。

样例输入
12121284000 12121285550
样例输出
5
样例解释
满足条件的号码有:12121285000、 12121285111、 12121285222、 12121285333、 12121285550。
对于 30% 的数据,R−L≤10^7
对于 100% 的数据,10^10 ≤L≤R< 10^11

【思路】

比较裸的数位dp。
我们思考转移时需要哪些信息,以及一些需要注意的细节。

  1. 题目中说4和8不能同时出现,所以我们需要保存4和8是否出现过。
  2. 必须出现三个以上的相邻且相同的数。因此我们需要保存前一个数是什么(pre),保存前一个数已经连续出现了几次(num)。
  3. 这道题要求了手机号码必须是11位,因此不能有前导零。
  4. 搜索到末状态时,我们不清楚是否出现过三个以上相邻且相等的数,因此我们需要保存一个bool来判断。

是不是很简单呀!!

#include
#include
#include
#include
#include
#include
#include
#include
#define re register
using namespace std;
long long a,b,c,n,m;
long long len=0;
long long q[101];
long long lo[101];
long long f[20][11][21][2][2][2];
long long dfs(long long pos,long long pre,long long num,bool law,bool lim,bool is4,bool is8,bool zero)
{
	if(num>=3)law=1;
	if(!pos)return law;
	if(!lim && !zero &&f[pos][pre][num][law][is4][is8]!=-1)return f[pos][pre][num][law][is4][is8];
	long long up=lim?q[pos]:9;
	long long ans=0;
	long long don=zero?1:0;
	for(long long re i=don;i<=up;i++)
	{
		if(is4&&(i==8))continue;
		if(is8&&(i==4))continue;
		ans+=dfs(pos-1,i,(i==pre)?num+1:1,law,lim&&i==up,is4||(i==4),is8||(i==8),0);
	}
	if(!lim&&!zero) f[pos][pre][num][law][is4][is8]=ans;
	return ans;
}
long long ask(long long x)
{
	len=0;
	memset(q,0,sizeof(q));
	while(x)
	{
		q[++len]=x%10;
		x/=10;
	}
	return dfs(11,0,0,0,1,0,0,1);
}
int main()
{	
	memset(f,-1,sizeof(f));
	scanf("%lld%lld",&n,&m);
	printf("%lld",ask(m)-ask(n-1));
}

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