【BZOJ4521】[CQOI2016] 手机号码(数位DP)

点此看题面

大致题意: 问有多少个\(L\sim R\)的整数,满足至少有\(3\)个相邻的相同数字,且不能同时出现\(4\)\(8\)

数位\(DP\)

好久没写过数位\(DP\)了,结果不出所料写出一个无比智障的错误(枚举数码的时候居然写了一个小于\(9\)。。。)

感觉我的\(DP\)可能有些复杂,考虑设\(f_{x,k,g,t}\)表示当前枚举到第\(x\)位,\(k=0/1/2\)分别表示没有出现\(4\)\(8\)出现了\(4\)出现了\(8\)\(g\)表示当前正在连续的数字,\(t\)表示连续的数字有几个。

特殊地,令\(g=10,t=0\)表示已经存在\(3\)个相邻的相同数字。

则枚举时只要单独处理\(4\)\(8\)的情况即可。

由于位数是相同的,存在前导\(0\)的情况会被相互抵消掉,因此懒了点没有判前导\(0\)

代码

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 2000
#define LL long long
using namespace std;
int l,v[20];LL n,m,S[20][3][15][4];
I LL DP(CI x,CI k=0,RI g=-1,RI t=0,CI f=0)//数位DP,f表示是否严格小于
{
	if(t==3&&(g=10,t=0),f&&~S[x][k][g][t]) return S[x][k][g][t];if(!x) return g==10;LL res=0;//用g=10,t=0表示符合条件,边界直接返回
	k^2&&(f||v[x]>=4)&&(res+=DP(x-1,1,g^10?4:10,g^10?(g^4?1:t+1):0,f||v[x]>4));//单独处理4
	k^1&&(f||v[x]>=8)&&(res+=DP(x-1,2,g^10?8:10,g^10?(g^8?1:t+1):0,f||v[x]>8));//单独处理8
	for(RI i=0;i<=9;++i) i^4&&i^8&&(f||v[x]>=i)&&(res+=DP(x-1,k,g^10?i:10,g^10?(g^i?1:t+1):0,f||v[x]>i));//枚举其余数码
	return f&&(S[x][k][g][t]=res),res;//记忆化
}
I LL Get(LL x) {l=0;W(x) v[++l]=x%10,x/=10;v[l+1]=0;return memset(S,-1,sizeof(S)),DP(11);}//分到数组中然后DP
int main() {return scanf("%lld%lld",&n,&m),printf("%lld\n",Get(m)-Get(n-1)),0;}//差分
posted @ 2020-06-12 13:11  TheLostWeak  阅读(16)  评论(0编辑  收藏