2022.3.29-3.30总结 纯数位dp

P2602 [ZJOI2010] 数字计数
a n s = s u m b − s u m a − 1 ans=sum_{b}-sum_{a-1} ans=sumbsuma1
枚举每个数码依次进行数位dp

#include 
#define ll long long
using namespace std;

int a[15];
ll dp[15][15][2][2];

ll dfs(int len,int sum,bool differ,bool prezero,int num){
    if (len==0){
        return sum;
    }
    if (dp[len][sum][differ][prezero]>=0){
        return dp[len][sum][differ][prezero];
    }

    ll res=0;
    for (int i=0;i<=9;i++){
        if (!differ&&i>a[len]) break;
        if (differ){
            if (!prezero){
                res+=dfs(len-1,sum+(i==num),1,0,num);
            }else{
                if (i==0) res+=dfs(len-1,sum,1,1,num);
                else res+=dfs(len-1,sum+(i==num),1,0,num);
            }
        }else{
            int now=(i<a[len]);
            if (!prezero){
                res+=dfs(len-1,sum+(i==num),now,0,num);
            }else{
                if (i==0) res+=dfs(len-1,sum,now,1,num);
                else res+=dfs(len-1,sum+(i==num),now,0,num);
            }
        }
    }
    return dp[len][sum][differ][prezero]=res;
}

ll solve(ll x,int num){
    memset(dp,-1,sizeof(dp));
    int len=0;
    while (x){
        a[++len]=x%10;
        x/=10;
    }
    return dfs(len,0,0,1,num);
}
int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    
    ll x,y;
    cin>>x>>y;
    for (int i=0;i<=9;i++){
        cout<<solve(y,i)-solve(x-1,i)<<" ";
    }
    return 0;
}

HDU - 2089 不要62
数位dp的基础上不去枚举4且存储一下上一位的数字是不是6即可

#include 
#include 
#define ll long long
using namespace std;
ll dp[15][2][2][2];
int a[10];

ll dfs(int len,bool differ,bool presix,int leadzero){
    if (len==0){
        return 1;
    }
    if (dp[len][differ][presix][leadzero]>=0){
        return dp[len][differ][presix][leadzero];
    }

    ll res=0;
    for (int i=0;i<=9;i++){
        if (!differ&&i>a[len]) break;
        if (i==4) continue;
        if (presix==1&&i==2) continue;

        if (differ){
            if (leadzero){
                if (i==0) res+=dfs(len-1,1,0,1);
                else res+=dfs(len-1,1,i==6,0);
            }else{
                res+=dfs(len-1,1,i==6,0);
            }
        }else{
            int now=(i<a[len]);
            if (leadzero){
                if (i==0) res+=dfs(len-1,1,0,1);
                else res+=dfs(len-1,now,i==6,0);
            }else{
                res+=dfs(len-1,now,i==6,0);
            }
        }
    }
    return dp[len][differ][presix][leadzero]=res;
}

ll solve(ll x){
    memset(dp,-1,sizeof(dp));
    int len=0;
    while (x){
        a[++len]=x%10;
        x/=10;
    }
    return dfs(len,0,0,1);
}

int main(){
    ll x,y;
    cin>>x>>y;
    while (x!=0||y!=0){
        if (x>y) swap(x,y);
        cout<<solve(y)-solve(x-1)<<"\n";
        cin>>x>>y;
    }
    return 0;
}

P2657 [SCOI2009] windy 数
在数位dp的基础上存储上一位数字,然后让当前位枚举的数字满足题意即可

#include 
#define ll long long
using namespace std;

ll dp[11][11][2][2];
int a[15];

ll dfs(int len,int pre,bool differ,bool prezero){
    if (len==0){
        return 1;
    }
    if (dp[len][pre][differ][prezero]>=0){
        return dp[len][pre][differ][prezero];
    }

    ll res=0;
    for (int i=0;i<=9;i++){
        if (!differ&&i>a[len])break;
        if (abs(i-pre)<2) continue;
        if (differ){
            if (prezero){
                if (i==0) res+=dfs(len-1,-2,1,1);
                else res+=dfs(len-1,i,1,0);
            }else{
                res+=dfs(len-1,i,1,0);
            }
        }else{
            int now=(i<a[len]);
            if (prezero){
                if (i==0) res+=dfs(len-1,-2,now,1);
                else res+=dfs(len-1,i,now,0);
            }else{
                res+=dfs(len-1,i,now,0);
            }
        }
    }
    return dp[len][pre][differ][prezero]=res;
}

ll solve(int x){
    int len=0;
    memset(dp,-1,sizeof(dp));
    while (x){
        a[++len]=x%10;
        x/=10;
    }
    return dfs(len,-2,0,1);
}
int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    
    int x,y;
    cin>>x>>y;
    cout<<solve(y)-solve(x-1);
    return 0;
}

SPOJ - MYQ10 Mirror Number
由题意数字中只有0,1,8能出现

判断镜像,在存储时用一个数组存储当前存储的所有数,再用一个realen表示当前数字实际的长度
当枚举的某一位时,如果该位处于前半段,则无其他限制;如果处于后半段,则需判断对应位置上的数是否相等

读入数据时用字符串处理

#include 
#define ll long long
using namespace std;

ll dp[50][50][2][2];
char a[50],b[50];

bool check(string s){
    int len=s.length();
    for (int i=0;i<=(len-1)/2;i++){
        if (s[i]!='1'&&s[i]!='0'&&s[i]!='8') return 0;
        if (s[i]!=s[len-1-i]) return 0;
    }
    return 1;
}

ll dfs(int len,int realen,bool differ,bool prezero){
    if (len==0){
        return 1;
    }
    if (differ&&dp[len][realen][differ][prezero]>=0){
        return dp[len][realen][differ][prezero];
    }

    ll res=0;
    for (int i=0;i<=9;i++){
        if (!differ&&i+'0'>a[len]) break;
        if (i!=0&&i!=1&&i!=8) continue;
        b[len]=i+'0';
        if (prezero){
            if (!i) res+=dfs(len-1,realen-1,differ|(i+'0'!=a[len]),1);
            else if (len>realen/2){
                res+=dfs(len-1,realen,differ|(i+'0'!=a[len]),0);
            } 
            else if (b[len]==b[realen-len+1]) res+=dfs(len-1,realen,differ|(i+'0'!=a[len]),0);
        }else{
            if (len>realen/2) res+=dfs(len-1,realen,differ|(i+'0'!=a[len]),0);
            else if (b[len]==b[realen-len+1]) res+=dfs(len-1,realen,differ|(i+'0'!=a[len]),0);
        }
    }
    // if (differ)
    return dp[len][realen][differ][prezero]=res;
}

ll solve(string ss){
    int len=0;
    for (int i=ss.length()-1;i>=0;i--){
        a[++len]=ss[i];
    }
    return dfs(len,len,0,1);
}
int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);

    memset(dp,-1,sizeof(dp));

    int T;
    cin>>T;
    while (T--){
        string s1,s2;
        cin>>s1>>s2;
        cout<<solve(s2)-solve(s1)+check(s1)<<"\n";    
    }
    return 0;
}

P4127 [AHOI2009]同类分布
因为各位数字和很小,枚举各位数之和mod进行数位dp,记录每一位数字之和,记录当前数字大小不断对mod取余,最后必须满足各位数之和为mod且数字大小取余后为0

#include 
#define ll long long
using namespace std;
int a[25];
ll dp[25][170][170][2][2];

ll dfs(int len,int numsum,int summod,bool differ,bool prezero,int mod){
    if (len==0){
        if (summod==0&&numsum==mod){
            return 1; 
        }
        return 0;
    }
    if (dp[len][numsum][summod][differ][prezero]>=0){
        return dp[len][numsum][summod][differ][prezero];
    }

    ll res=0;
    for (int i=0;i<=9;i++){
        if (!differ&&i>a[len])break;
        if (prezero){
            if (!i) res+=dfs(len-1,numsum,summod,differ|(i!=a[len]),1,mod);
            else res+=dfs(len-1,numsum+i,(summod*10+i)%mod,differ|(i!=a[len]),0,mod);
        }else{
            res+=dfs(len-1,numsum+i,(summod*10+i)%mod,differ|(i!=a[len]),0,mod);
        }
    }
    return dp[len][numsum][summod][differ][prezero]=res;
}


ll solve(ll x){
    int len=0;
    while (x){
        a[++len]=x%10;
        x/=10;
    }

    ll ans=0;
    for (int i=1;i<=9*len;i++){
        memset(dp,-1,sizeof(dp));
        ans+=dfs(len,0,0,0,1,i);
    }
    return ans;

}
int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);


    ll x,y;
    cin>>x>>y;
    cout<<solve(y)-solve(x-1)<<"\n";
    return 0;
}

P3413 SAC#1 - 萌数
这道题要存储一下之前选的数是什么,然后枚举到该位时只需要查看一下上一位和上上一位的数字是否与该位相等,如果相等则这个数一定是萌数,属数位dp统计即可

#include 
#define ll long long
using namespace std;

const ll mod=1e9+7;

ll dp[1005][1005][2][2];
char a[1005],b[1005];

bool check(string s){
    for (int i=0;i<s.length();i++){
        if (i>0&&s[i]==s[i-1]) return 1;
        if (i>1&&s[i]==s[i-2]) return 1;
    }
    return 0;
}

ll dfs(int len,int realen,bool differ,bool prezero){
    if (len==0){
        return 1;
    }
    if (differ&&dp[len][realen][differ][prezero]>=0){
        return dp[len][realen][differ][prezero];
    }

    ll res=0;
    for (int i=0;i<=9;i++){
        if (!differ&&i+'0'>a[len])break;
        b[len]=i+'0';
        if (prezero){
            if (!i) res=(res+dfs(len-1,realen-1,differ|(i+'0'!=a[len]),1))%mod;
            else res=(res+dfs(len-1,realen,differ|(i+'0'!=a[len]),0))%mod;
        }else{
            if (b[len]!=b[len+1]){
                if (len+2<=realen){
                    if (b[len]!=b[len+2]){
                        res=(res+dfs(len-1,realen,differ|(i+'0'!=a[len]),0))%mod;
                    }
                }else{
                    res=(res+dfs(len-1,realen,differ|(i+'0'!=a[len]),0))%mod;
                }
            }
        }
    }
    if (differ) dp[len][realen][differ][prezero]=res;
    return res;
}

ll solve(string ss){
    int len=0;
    for (int i=ss.length()-1;i>=0;i--){
        a[++len]=ss[i];
    }
    return dfs(len,len,0,1)%mod;
}
int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);

    memset(dp,-1,sizeof(dp));

    string s1,s2;
    cin>>s1>>s2;
    ll temp=0,ans=0;

    for (int i=0;i<s2.length();i++){
        ans=(ans*10+s2[i]-'0')%mod;
    }
    for (int i=0;i<s1.length();i++){
        temp=(temp*10+s1[i]-'0')%mod;
    }

    ans=(ans-temp+mod)%mod;
    cout<<(ans-(solve(s2)-solve(s1)+mod)%mod+mod)%mod+check(s1)<<"\n";    
    return 0;
}

HDU - 6148 Valley Numer
记录一下当前是递增的还是递减的,只能由递减转为递增。
对于有最高位,它的递减递增状态不确定 ( g r e = = 2 ) (gre==2) gre==2,应由下一位决定
数位dp即可

#include 
#include 
#define ll long long
using namespace std;
const ll mod=1e9+7;


ll dp[105][12][3][2][2];
int a[105];

ll dfs(int len,int prenum,int gre,bool differ,bool prezero){
    if (len==0&&!prezero){
        return 1;
    }
    if (differ&&dp[len][prenum][gre][differ][prezero]>=0){
        return dp[len][prenum][gre][differ][prezero];
    }

    ll res=0;
    for (int i=0;i<=9;i++){
        if (!differ&&i>a[len]) break;
        if (prezero){
            if (!i) res=(res+dfs(len-1,-1,2,1,1))%mod;
            else res=(res+dfs(len-1,i,2,differ|(i!=a[len]),0))%mod;
        }else{
            if (gre==2){
                int now=(i==prenum?2:i>prenum);
                res=(res+dfs(len-1,i,now,differ|(i!=a[len]),0))%mod;
            }else if (gre==0){
                res=(res+dfs(len-1,i,i>prenum,differ|(i!=a[len]),0))%mod;
            }else if (i>=prenum){
                res=(res+dfs(len-1,i,1,differ|(i!=a[len]),0))%mod;
            }
        }
    }

    if (differ) dp[len][prenum][gre][differ][prezero]=res;
    return res;

}

ll solve(string s){
    int len=0;
    for (int i=s.length()-1;i>=0;i--){
        a[++len]=s[i]-'0';
    }
    return dfs(len,-1,2,0,1);
}

int main(){
    memset(dp,-1,sizeof(dp));
    
    int T;
    cin>>T;
    while (T--){
        string s;
        cin>>s;
        // ll ans=0;
        // for (int i=0;i
        //     ans=(ans*10+s[i]-'0')%mod;
        // }
        cout<<solve(s)<<"\n";
        // cout<<(ans-solve(s)+mod)%mod<<"\n";
    }

    return 0;
}


CF628D Magic Numbers
记录一下最高位是奇数还是偶数,那么就能推出奇偶数位哪些必须是 d d d,哪些必不是 d d d

#include 
#define ll long long
using namespace std;
const ll mod=1e9+7;
int m,d;

ll dp[2005][2][2005][2][2];
int a[2005];

bool check(string s){
    int summod=0;
    for (int i=0;i<s.length();i++){
        if (i%2==0&&s[i]-'0'==d) return 0;
        if (i%2==1&&s[i]-'0'!=d) return 0;
        summod=(summod*10+s[i]-'0')%m;
    }
    return summod==0;
}

ll dfs(int len,bool pre,int summod,bool differ,bool prezero){
    if (len==0){
        return 1LL*summod==0&&prezero==0;
    }
    if (differ&&dp[len][pre][summod][differ][prezero]>=0){
        return dp[len][pre][summod][differ][prezero];
    }

    ll res=0;
    for (int i=0;i<=9;i++){
        if (!differ&&i>a[len]) break;
        
        if (prezero){
            if (!i) res=(res+dfs(len-1,0,0,1,1))%mod;
            else if (i!=d){
                res=(res+dfs(len-1,len%2,i%m,differ|(i!=a[len]),0))%mod;
            }
        }else{
            if (pre){
                if ((len%2==0&&i==d)||(len%2==1&&i!=d)){
                    res=(res+dfs(len-1,pre,(summod*10+i)%m,differ|(i!=a[len]),0))%mod;
                }
            }else{
                if ((len%2==1&&i==d)||(len%2==0&&i!=d)){
                    res=(res+dfs(len-1,pre,(summod*10+i)%m,differ|(i!=a[len]),0))%mod;
                }
            }
        }
    }
    if (differ) dp[len][pre][summod][differ][prezero]=res;
    return res;
}

ll solve(string s){
    int len=0;
    for (int i=s.length()-1;i>=0;i--){
        a[++len]=s[i]-'0';
    }
    return dfs(len,0,0,0,1);
}

int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    
    memset(dp,-1,sizeof(dp));

    cin>>m>>d;
    string x,y;
    cin>>x>>y;
    cout<<(solve(y)-solve(x)+check(x)+mod)%mod;
    return 0;
}

CF55D Beautiful numbers
这道题比较妙;

考虑朴素的数位dp做法,我们要统计各位数的最小公因子,还有该数字的大小为多少,数据范围1e18,这个数组肯定开不下,那么我们考虑优化一下:

首先考虑1~9所有数的最小公倍数2520。
假设现在的数是 m x mx mx(各位数字的最小公倍数是 x x x),由于 2520 = n x 2520=nx 2520=nx ,那么对于 m x % 2520 = ( m − ⌊ m n ⌋ ) ∗ x mx\%2520=(m-\lfloor {m \over n} \rfloor)*x mx%2520=(mnm⌋)x
也就是说当一个数 a a a 的某个因子 x x x能整除某个数 y y y 时,
y % a ≡ y ( m o d y\%a \equiv y (mod y%ay(mod x ) x) x) i f ( y % x = = 0 ) if (y\%x==0) if(y%x==0)

因此我们只需在不断更新数字大小的同时对2520取模,最后判断取模后的数能否被各位数字的最小公倍数整除即可,1e18的空间缩小到了2520

但是我们还需记录各位数字的最小公倍数为多少,这个空间也是2520,最受dp开ll会超出空间限制,所以我们考虑继续优化空间。

对于记录各位数字的最小公因数,我们会发现其情况只有40多种,也就是2520的因子只有40多种,因此可以将记录最小公因数这一维离散化处理缩小到50

#include 
#define ll long long
using namespace std;

const int mod=2520;

int mp[2520],cnt;
ll dp[20][50][2520][2][2];

int a[20];

ll dfs(int len,int lcm,int summod,bool differ,bool prezero){
    if (len==0){
        if (prezero==1||lcm==0) return 0;
        return summod%lcm==0;
    }
    if (differ&&dp[len][mp[lcm]][summod][differ][prezero]>=0){
        return dp[len][mp[lcm]][summod][differ][prezero];
    }

    ll res=0;
    for (int i=0;i<=9;i++){
        if (!differ&&i>a[len]) break;
        if (prezero){
            if (!i) res+=dfs(len-1,0,0,1,1);
            else res+=dfs(len-1,i,i,differ|(i!=a[len]),0);
        }else{
            int now=lcm;
            if (i) now=i*lcm/__gcd(i,lcm);
            res+=dfs(len-1,now,(summod*10+i)%mod,differ|(i!=a[len]),0);
        }
    }
    if (differ) dp[len][mp[lcm]][summod][differ][prezero]=res;
    return res;
}

ll solve(ll x){
    int len=0;
    while (x){
        a[++len]=x%10;
        x/=10;
    }
    return dfs(len,0,0,0,1);
}
int main(){

    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);

    for (int i=1;i<=2520;i++){
        if (2520%i==0){
            mp[i]=++cnt;
        }
    }


    memset(dp,-1,sizeof(dp));

    int T;
    cin>>T;
    while (T--){
        ll x,y;
        cin>>x>>y;
        cout<<solve(y)-solve(x-1)<<"\n";
    }
    return 0;
}

总结:
数位dp一般是统计某个区间内满足某个条件的数有多少,数据范围一般很大,可用字符串处理,从高位开始记忆化搜索

你可能感兴趣的:(dp,深度优先,算法)