1. 程式人生 > >[數位DP][CQOI2016]手機號碼(附數位DP模板)

[數位DP][CQOI2016]手機號碼(附數位DP模板)

個數 是否 ... string using 習慣 不同的 pac 轉移

首先,我們要看出來這是一道數位DP題。。。
  1. 有上下界l,r;
  2. 只與數字的排列有關,與數字大小無關。
  3. 對於數字之間有諸多限制。
  4. 一般範圍是在18位左右,此題11位,顯然可以。
所以,FYJ說這是一道裸題。(Orz) 然後就很套路了。 在此先附一個數位模板,基本上大部分數位DP都是這個框架。(轉載自某大佬blog,我覺得他應該不會和蒟蒻計較的。QwQ) 我在他的模板的基礎上按照自己的習慣改了一下下...(建議看一看我的,我加了自己的補充。
#include<iostream>
#include<stdio.h>
#include<cstring>
#define
LL long long using namespace std; LL dp[11][11][11][2][2][2]; int a[11];int cnt; inline LL calc(int pos,int llast,int last,bool if4,bool if8,bool if3,bool head,bool limit){ if(pos==-1) return if3?1:0;//如果出現過連續三個相同的數,才符合條件。 if(!limit && !head && last!=-1 && llast!=-1
&& dp[pos][llast][last][if4][if8][if3]!=-1) return dp[pos][llast][last][if4][if8][if3]; int up=limit?a[pos]:9;//上限。 int down=head?1:0;//下限。 LL tmp=0; for(int i=down;i<=up;++i){ if(if4 && i==8) continue;//有4不能有8. if(if8 && i==4) continue;//
有8不能有4. tmp+=calc(pos-1,last,i,if4||(i==4),if8||(i==8),if3||(llast==last&&last==i),0,limit && i==a[pos]); //傳遞條件就好啦。 } if(!limit && !head && last!=-1 && llast!=-1) dp[pos][llast][last][if4][if8][if3]=tmp; return tmp; } inline LL devide(LL x){ cnt=-1; memset(a,0,sizeof a); while(x){ a[++cnt]=x%10; x/=10; } return cnt!=10?0:calc(cnt,-1,-1,0,0,0,1,1);//不足11位返回零就好啦。 //不要說不存在這種情況。 //因為我們傳的是l-1。所以l=100 0000 0000 時,l-1=99 9999 9999 辣。 } int main(){ LL l,r; cin>>l>>r; memset(dp,-1,sizeof dp); cout<<devide(r)-devide(l-1); return 0; }
可能是我太弱的原因,我感覺數位dp就是記憶化搜索嘛。。。 不過話說dp的本質就是把不同的狀態用同一種狀態表示,方便轉移。 那麽對於這道題,需要註意的狀態如下:
  1. 不能同時出現4和8,於是就多了兩個bool型, bool if4,bool if8,表示4,8是否出現過。
  2. 要有三個連續的,所以就多了,int llast:上上個數,int last:上個數,bool if3:三個連續的是否出現
於是你就有了一個六維dp辣~~:dp[pos][llast][last][if4][if8][if3]. 具體看代碼。
#include<iostream>
#include<stdio.h>
#include<cstring>
#define LL long long
using namespace std;

LL dp[11][11][11][2][2][2];

int a[11];int cnt;

inline LL calc(int pos,int llast,int last,bool if4,bool if8,bool if3,bool head,bool limit){
    if(pos==-1) return if3?1:0;//如果出現過連續三個相同的數,才符合條件。 
    if(!limit && !head && last!=-1 && llast!=-1 && dp[pos][llast][last][if4][if8][if3]!=-1)
        return dp[pos][llast][last][if4][if8][if3];
    int up=limit?a[pos]:9;//上限。 
    int down=head?1:0;//下限。 
    LL tmp=0;
    for(int i=down;i<=up;++i){
        if(if4 && i==8) continue;//有4不能有8. 
        if(if8 && i==4) continue;//有8不能有4. 
        tmp+=calc(pos-1,last,i,if4||(i==4),if8||(i==8),if3||(llast==last&&last==i),0,limit && i==a[pos]);
        //傳遞條件就好啦。 
    }
    if(!limit && !head && last!=-1 && llast!=-1) dp[pos][llast][last][if4][if8][if3]=tmp;
    return tmp;
}

inline LL devide(LL x){
    cnt=-1;
    memset(a,0,sizeof a);
    while(x){
        a[++cnt]=x%10;
        x/=10;
    }
    return cnt!=10?0:calc(cnt,-1,-1,0,0,0,1,1);//不足11位返回零就好啦。
    //不要說不存在這種情況。
    //因為我們傳的是l-1。所以l=100 0000 0000 時,l-1=99 9999 9999 辣。 
    
}

int main(){
    LL l,r;
    cin>>l>>r;
    memset(dp,-1,sizeof dp);
    cout<<devide(r)-devide(l-1);
    return 0;
}
就這樣了吧。因為本人實在太弱,錯了是很正常的,歡迎指正。不過希望能幫到你哦。

[數位DP][CQOI2016]手機號碼(附數位DP模板)