其他分享
首页 > 其他分享> > [CQOI2016]手机号码

[CQOI2016]手机号码

作者:互联网

[CQOI2016]手机号码

洛谷题目链接

挺经典的一道数位dp题。


题目大意

输入\(l,r\),我们需要求出区间\([l,r]\)内所有包含三个连续相同数字并且不同时包含\(8,4\)的\(11\)位电话号码,需要注意的是,不含前导零并且一定是\(11\)位数字,即最高位大于等于\(1\)。


题解

数位dp的思路就是逐位确定数字,用来处理带有特殊需求的一串数字的问题,常见的有求数量的,这道题就是一道板子题,我们处理数位dp,只需要确定dp数组的所有维基本上就敲代码就可以了。

dp[pos][pre][last][have_8][have_4][sta];

每一维依次代表:

  1. 查找到pos位。2.上一位是pre。 3.上上一位是last。4.是否已经存在8。5.是否已经存在4。6.是否已经有连续的三位上的数相同。

看起来好像很多,其实知道了套路没什么新颖的。

我们在dp(dp ×,dfs √)注意每一位可能是有限制的,比如\(r=68\)​​​那么十位上最多大为\(6\)​​,如果十位小于\(6\)​​​,这时个位上可以是\([0,9]\)​,但是如果十位上是\(6\)​,那么个位上只能是\([0,8]\)​所以我们需要变量\(limit\)来判断当前取值是否有上限,注意,由于不含前导零,所有最高位不能是\(0\),所以最高位有下限。

其实说它是dp,大部分时候一般写作记忆化搜索的形式,当然你可以写成递推的形式,就是特别毒瘤。

#include<bits/stdc++.h>
using namespace std;
long long l,r;
int dp[15][15][15][2][2][2],a[15];
long long dfs(int pos,int pre,int last,bool have_8,bool have_4,bool limit,bool sta){
	if(pos==0)return sta?1:0;
	if(pos!=11&&pre!=-1&&last!=-1&&dp[pos][pre][last][have_8][have_4][sta]!=-1&&!limit)return dp[pos][pre][last][have_8][have_4][sta];
	int up=limit?a[pos]:9;
	long long ans=0;
	for(int i=(pos==11?1:0);i<=up;++i){
		if(have_8&&i==4)continue;
		if(have_4&&i==8)continue;
		ans+=dfs(pos-1,i,pre,have_8||(i==8),have_4||(i==4),limit&&i==a[pos],(i==pre&&pre==last)||sta);
	}
	if(!limit&&pos!=11&&last!=-1&&pre!=-1)dp[pos][pre][last][have_8][have_4][sta]=ans;
	return ans;
}
long long calc(long long x){
	memset(dp,-1,sizeof(dp));
	for(int i=1;i<=11;++i){
		a[i]=x%10;
		x/=10;
	}
	return a[11]==0?0:dfs(11,-1,-1,0,0,1,0);//因为l-1可能是十位数
}
signed main(){
	scanf("%lld%lld",&l,&r);
	printf("%lld\n",calc(r)-calc(l-1));
	return 0;
}

标签:pre,last,int,pos,long,手机号码,CQOI2016,dp
来源: https://www.cnblogs.com/fanner-Blog/p/15153806.html