LOJ#2044. 「CQOI2016」手机号码 数位dp

这道题中我们要考虑这么几件事情:

1.前 2 个数是什么.
2.是否已经合法了.
3.是否顶上界.
4.是否有4/8
那么设状态的时候就是 $f[num_{i-1}][num_{i-2}][state][l][4][8]$
记忆化搜索的时候如果搜过该状态且补丁上界就返回.
然后转移的时候注意:
1.是否新出现 4
2.是否新出现 8
3.是否顶上界
4.当前位置加入数字

code: 

#include <bits/stdc++.h>  
#define ll long long 
#define setIO(s) freopen(s".in","r",stdin) 
using namespace std;   
int num[20],cnt; 
ll dp[20][20][20][2][2][2][2],L,R;    
// dp[当前位置][前一个位置的数][前 2 个位置的数][是否合法][有 4][有 8][是否顶上界]   
ll calc(int pos,int a,int b,bool state,bool f,bool e,bool li) 
{
    if(f&&e) return 0;   
    if(!pos) return state;   
    if(!li&&dp[pos][a][b][state][f][e][li]!=-1)   
        return dp[pos][a][b][state][f][e][li];  
    ll det=0;    
    int m=li?num[pos]:9;    
    for(int i=0;i<=m;++i)  
        det+=calc(pos-1,i,a,state||(i==a&&i==b),f||(i==4),e||(i==8),li&&(i==m));    
    if(!li) dp[pos][a][b][state][f][e][li]=det;    
    return det; 
}    
ll sol(ll x) 
{ 
    cnt=0,memset(num,0,sizeof(num));  
    do
    {
        num[++cnt]=x%10; 
        x/=10; 
    }while(x);    
    if(cnt!=11) return 0; 
    ll ans=0; 
    memset(dp,-1,sizeof(dp));          
    for(int i=1;i<=num[cnt];++i)  
        ans+=calc(cnt-1,i,0,0,i==4,i==8,i==num[cnt]);   
    return ans;  
}
int main() 
{ 
    // setIO("input");     
    scanf("%lld%lld",&L,&R);   
    printf("%lld\n",sol(R)-sol(L-1));   
    return 0; 
}

  

posted @ 2020-06-12 10:50  EM-LGH  阅读(139)  评论(0编辑  收藏  举报