zoukankan      html  css  js  c++  java
  • 数位dp整理

    数位dp的思想就在于递归,记录当前的某一个唯一状态,依次递归下去,要注意唯一。

    数位dp常设的状态有当前位置,上一数字,是否具有前导零,是否有限制。

    1.CodeForces 55DBeautiful numbers

    题目大意:一个数是幸运数当且仅当这个数能整除所有位数,求[a,b]有多少幸运数

    10以内的数的最小公倍数是2520,状态为当前位置,模2520的值,出现了哪些数字(状压,1,0删去)

    #include<iostream>
    #include<stdio.h>
    #include<string.h>
    using namespace std;
    const int maxa = 20;
    const int mod = 2520;
    long long  dp[maxa][1<<8][mod];
    #define LL long long
    //当前位置,限制,有哪些数,模
    int num[maxa];
    LL dfs(int pos, int limit, int val, int numb){
        if(pos == 0){
            for(int i = 2; i < 10; i++){
                if((1<<(i-2)) & val){
                    if(numb % i) return 0;
                }
            }return 1;
        }
        if(!limit && dp[pos][val][numb] != -1) return dp[pos][val][numb];
        int nn = limit?num[pos]:9;
        LL res = 0;
        for(int i = 0; i <= nn; i++){
            res += dfs(pos-1, limit & (i ==nn), i<2?val:val|(1<<(i-2)), (numb*10+i)%mod);
        }
        if(!limit) dp[pos][val][numb] = res;
        return res;
    }
    LL ANS(LL n){
        int leng = 0;
        while(n){
            num[++leng] = n %10;
            n /= 10;
        }
        return dfs(leng, 1, 0, 0);
    }
    int main(){
        int t;
        LL a, b;
        memset(dp, -1, sizeof(dp));
        scanf("%d", &t);
        while(t--){
            cin>>a>>b;
            if(a > b)swap(a, b);
            cout<<ANS(b) - ANS(a-1)<<endl;
        }
    }
    View Code

     2.HDU 4352

    题目大意,求最长上升序列为k的数在[a,b]出现了多少次,

    lis的log时间的思想是换掉长度和当前数字一样并且大于当前的数字的数,用的是对列维护的

    在这里我们可以这样比如当前最长是1346这个队列,当前数字是2,就能用2区替换3,因为到2的最长序列和3是一样的并且2还比三小,队列就变成了1246

    状态是位数,序列(状压掉),K(是为了以后出现K省时间

    #include<iostream>
    #include<string.h>
    #include<stdio.h>
    using namespace std;
    #define LL long long
    const int maxa = 20;
    LL dp[maxa][1<<10][11];
    int num[maxa];
    int Next[1<<10][10];
    int Hash[1<<10];
    int K;
    int go(int now, int k){
        int kk = -1;
        for(int i = k; i < 10; i ++){
           if((1<<i) & now){
              kk = i;
             break;
           }
        }
        if(kk == -1){
            return now|(1<<k);
        }
        now ^= (1<<kk);
        now |= (1<<k);
        return now;
    }
    void init(){
        memset(dp, -1, sizeof(dp));
        for(int i = 0; i < (1<<10); i++){
            for(int k = 0; k < 10; k++){
                if(i&(1<<k))
                    Hash[i] ++;
            }
        }
        for(int i = 0; i < (1<<10); i++){
            for(int k = 0; k < 10; k++){
                Next[i][k] = go(i, k);
            }
        }
    }
    LL dfs(int pos, int mask, int isZero, int limit){//printf("%d %d %d %d
    ", pos, mask, isZero, limit);
        if(pos ==0){//printf("%d %d
    ", Hash[mask], K == mask);
            return Hash[mask] == K;
        }
        if(!limit && -1!=dp[pos][mask][K])
            return dp[pos][mask][K];
    
        int nn = limit?num[pos]:9;
        LL res = 0;
        for(int i = 0; i <= nn; i++){
            res += dfs(pos-1,( isZero && (i ==0))? mask:Next[mask][i],isZero&(i==0), limit &(i ==nn));
        }
        if(!limit){
            dp[pos][mask][K] = res;
        }
        return res;
    }
    LL ANS(LL n){
        int leng = 0;
        while(n){
            num[++leng] = n%10;
            n/= 10;
        }
        return dfs(leng, 0, 1, 1);
    }
    int main(){
        init();
        int t;
        int Case = 1;
        LL a, b;
        scanf("%d", &t);
        while(t--){
            cin>>a>>b>>K;
            cout <<"Case #"<<Case++<<": "<<ANS(b) - ANS(a-1)<<endl;
        }
        return 0;
    }
    View Code

    3.HDU 2089

    题意:求[a,b]有多少数字里有62或含有4

    状态,位数,到当前为满足或不满足

    #include<iostream>
    #include<string.h>
    #include<stdio.h>
    using namespace std;
    const int maxa = 10;
    int dp[maxa][maxa][2];
    int num[maxa];
    int dfs(int pos, int limit, int ye, int last){
        if(pos == 0){
            return ye;
        }
        if(!limit && -1 != dp[pos][last][ye])
            return dp[pos][last][ye];
        int nn = limit ? num[pos]:9;
        int res = 0;
        for(int i = 0; i <= nn; i++){
            res += dfs(pos-1, limit & (i==nn),i == 4 | ye | (last == 6&& i ==2), i);
        }
        if(!limit){
            dp[pos][last][ye] = res;
        }
        return res;
    }
    int ans(int a){
        int leng = 0;
        while(a){
            num[++leng] = a%10;
            a /=10;
        }
        return dfs(leng, 1, 0, 0);
    }
    int main(){
        memset(dp, -1, sizeof(dp));
        int a, b;
        while(scanf("%d%d", &a, &b), a+b){
            cout<<b-a+1-(ans(b) - ans(a-1))<<endl;
        }
        return 0;
    }
    View Code

    4.HDU 3555

    和3思路一模一样

    #include<iostream>
    #include<string.h>
    #include<stdio.h>
    using namespace std;
    const long long maxa = 20;
    long long dp[maxa][maxa][2];
    long long num[maxa];
    long long dfs(long long pos, long long limit, long long ye, long long last){
        if(pos == 0){
            return ye;
        }
        if(!limit && -1 != dp[pos][last][ye])
            return dp[pos][last][ye];
        long long nn = limit ? num[pos]:9;
        long long res = 0;
        for(long long i = 0; i <= nn; i++){
            res += dfs(pos-1, limit & (i==nn),ye | (last == 4&& i ==9), i);
        }
        if(!limit){
            dp[pos][last][ye] = res;
        }
        return res;
    }
    long long ans(long long a){
        long long leng = 0;
        while(a){
            num[++leng] = a%10;
            a /=10;
        }
        return dfs(leng, 1, 0, 0);
    }
    int main(){
        memset(dp, -1, sizeof(dp));
        long long a, b, t;
        cin>>t;
        while(t--){
            cin>>a;
            cout<<ans(a)<<endl;
        }
        return 0;
    }
    View Code

    5.POJ 3252

    题目大意:求[a,b]有多少数字二进制0的个数>=1的个数

    注意前导零

    状态:位数,0比1多的个数,是否有前导零。

    #include<string.h>
    #include<stdio.h>
    #include<iostream>
    using namespace std;
    const int maxa = 100;
    int dp[maxa][2*maxa][2];
    int num[maxa];
    int dfs(int pos, int limit, int re, int isZero){
        //printf("%d %d %d %d
    ", pos, limit, re, isZero);
        if(pos == 0){//printf("%d
    ", (re >= 50));
            return (re >= 50);
        }
        if(!limit && dp[pos][re][isZero] != -1){
            //printf("%d %d %d %d %d
    ", pos, limit, re, isZero, dp[pos][re]);
            return dp[pos][re][isZero];
        }
        int nn = limit ? num[pos]:1;
        int res = 0;
        for(int i = 0;i <= nn ; i++){
           res += dfs(pos-1, limit &(i ==nn), (isZero && i==0)?re:(i==0?re+1:re-1), isZero && (i==0));
        }
        if(!limit)
            dp[pos][re][isZero] = res;
        return res;
    }
    int ans(int n){
        int leng = 0;
        while(n){
            num[++leng] = n %2;
            n /= 2;
        }
        return dfs(leng, 1, 50, 1);
    }
    int main(){
    
        memset(dp, -1, sizeof(dp));
        int a, b;
        //printf("%d
    ", ans(12));
        while(scanf("%d%d", &a, &b)!=EOF){
            //printf("%d %d
    ", ans(b), ans(a-1));
            printf("%d
    ",ans(b) - ans(a-1));
        }
        return 0;
    }
    View Code

    6.HDU 3709
    题目大意:一个数十平衡数的条件是将这个数看成是一排砝码,天平的支点在某一个数字上,能使两边是平衡的

    hint:4139 is a balanced number with pivot fixed at 3. The torqueses are 4*2 + 1*1 = 9 and 9*1 = 9

    问在[a, b]区间有多少数是平衡数.

    思路dp[][][],四维分别是,位置 ,中间支点的位置,左边减去右边的值

    需要注意的就是前导零问题,好久没刷数位dp的题被这个卡了半天

    #include<iostream>
    #include<string.h>
    #include<cmath>
    #include<stdio.h>
    using namespace std;
    const int maxa = 20;
    const int maxn = 2000;
    long long dp[maxa][10][maxa][maxn];
    //位置,当前位置的数字,中间支点的位置,左边减去右边的值
    int N[maxa];
    long long dfs(int len, int nn, int point, int num, int limit, int zero){
        if(len == -1){
            return num == 0 && zero == 0;
        }
        if(num < 0)return 0;
        if(!zero && !limit && dp[len][nn][point][num]!= -1){//printf("+");
            return dp[len][nn][point][num];
        }
        int l = limit?N[len]:9;
        long long sum = 0;
        for(int i = 0; i <= l; i++){//printf("+");
            sum += dfs(len-1, i, point, (len - point) * i+num, limit && (i == l), zero && (i==0));
        }
        if(!limit && !zero) dp[len][nn][point][num] = sum;
        return sum;
    }
    long long ans(long long n){
        if(n < 0)return 0;
        int o = 0;
        while(n){
            N[o++] = n%10;
            n /= 10;
        }
        long long sum = 0;
        for(int i = 0;i < o; i++){
            sum += dfs(o-1, 0, i, 0, 1, 1);
        }
        sum ++;
        return sum;
    }
    int main(){
        memset(dp, -1, sizeof(dp));
        //cout<<ans((long long)(9))<<endl;return 0;
        int t;
        scanf("%d", &t);
        while(t--){
            long long a, b;
            cin>>a>>b;
            //cout<<ans(b)<<endl;
            cout<<ans(b) - ans(a-1)<<endl;
        }
    }
    View Code

     7.HDU 3652

    题目大意:一个数是lala数时,这个数作为字符串里面含有13,并且这个数能被13整除

    思路:
    int dp[maxa][2][2][13];
    //位置,上一个数是否是1,是否之前已经凑齐13,当前模13的值

    #include<stdio.h>
    #include<string.h>
    #include<iostream>
    #include<cmath>
    using namespace std;
    const int maxa = 12;
    int dp[maxa][2][2][13];
    //位置,上一个数是否是1,是否之前已经凑齐13,当前模13的值
    int nn[maxa];
    int dfs(int pos, bool one, bool lala, int num, bool limit){
        if(pos == -1){
            return lala == 1 && num == 0;
        }
        if(!limit && dp[pos][one][lala][num] != -1)return dp[pos][one][lala][num];
        int l = limit ? nn[pos]: 9;
        int sum = 0;
        for(int i = 0; i <= l; i++){
            sum += dfs(pos-1, i == 1, lala || (one && i == 3), (int(num + i*pow(10, pos)))% 13, limit && i == l);
        }
        if(!limit){
            dp[pos][one][lala][num] = sum;
        }
        return sum;
    }
    int ans(int n){
        int o = 0;
        while(n){
            nn[o++] = n%10;
            n /= 10;
        }
        return dfs(o-1, 0, 0, 0, 1);
    }
    int main(){
        memset(dp, -1, sizeof(dp));
        int n;
        while(scanf("%d", &n)!=EOF){
            printf("%d
    ", ans(n));
        }
    }
    View Code
  • 相关阅读:
    Java多线程运行机制的基本原理
    JAVA IO中的设计模式
    C++ 内存泄露处理方法 (转)
    使用PageHeap.EXE或GFlags.EXE检查内存越界错误 (转)
    32位Windows7上8G内存使用感受+xp 32位下使用8G内存 (转)
    windows 32位系统中进程最大可用内存空间为3GB (转)
    OpenGL 4.0 GLSL 实现 投影纹理映射(Projective Texture Mapping) (转)
    DebugView 调试工具
    剑桥中国史:“571”——林彪的未遂政变(转)
    邱会作与儿子对话:林彪的莫须有之罪(转)
  • 原文地址:https://www.cnblogs.com/icodefive/p/4437999.html
Copyright © 2011-2022 走看看