数位dp进阶:[CQOI2016]手机号码

我自己居然写对了!!!!

这道题目我们要充分考虑所有情况。首先要求三位连续的相同,我们开一个bool变量,来记录是否已经满足了条件。有一个小细节:

if (len<=0) return tre;

这样如果搜完了,满足条件就返回1,否则为0.

另外,4和8不能同时出现在号码中,所以开两个bool数组,来记录。同时,记忆化的细节要注意,必须满足所有条件才能return。

还有,就是第一位一定不是0,直接在一开始枚举1-num[cnt]就好了,注意如果i!=num[cnt],limit要设为0;

code:

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <cmath>       
#define int long long        
using namespace std;
const int maxn=14;
int l,r,num[99],dp[maxn][maxn][maxn][maxn][2][2],cnt;
inline int dfs(int len,int firs,int sec,bool lmt,bool tre,bool eight,bool four)
{ 
    int shit=9;
    if (lmt) shit=num[len];
    int ans=0;
    if (four&&eight) return 0;
    if (len<=0) return tre;
    if (~dp[len][firs][sec][tre][eight][four]&&!lmt) return dp[len][firs][sec][tre][eight][four];
    for (int i=0;i<=shit;++i)
    {
    	ans+=dfs(len-1,sec,i,lmt&&i==shit,tre||( (i==sec)&&(i==firs)),(i==8)||eight,(i==4)||four);
    }
    dp[len][firs][sec][tre][eight][four]=ans;
    return ans;
}
signed main()
{
    cin>>l>>r;
    while (r!=0)
    {
        num[++cnt]=r%10;
        r/=10;
    }
    memset(dp,-1,sizeof(dp));
    int fina1=0,fina2=0;
    for (int i=1;i<=num[cnt];++i)
    {
        fina1+=dfs(cnt-1,0,i,i==num[cnt],0,i==8,i==4); 	
    }
    memset(dp,-1,sizeof(dp));
    memset(num,0,sizeof(num));
    cnt=0;  
    l--;  
	if (l>=1e10)
	{		               
    	while (l!=0) 
    	{
        	num[++cnt]=l%10;
        	l/=10;
    	}	
    	for (int i=1;i<=num[cnt];++i)
    	{
        	fina2+=dfs(cnt-1,0,i,i==num[cnt],0,i==8,i==4); 	
    	} 	
    }
	cout<<fina1-fina2<<endl;
    return 0;
}

收获:

数位dp有三个需要斟酌的点:1、初始状态。2、判断是否合法(扫完后如果合法return 1)。3、记忆化(注意一定要满足所有条件并且limit==0才可以转移)

posted @ 2018-09-20 17:37  Splitor  阅读(127)  评论(0编辑  收藏  举报