• 数位dp的学习


     统计一个区间[le,ri]内满足一些条件数的个数

    实质:
    暴力枚举->记忆化->数位dp

    通常两种解法:

    (以bzoj1833为例)
    1、记忆化搜索

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #include<cmath>
    #include<queue>
    #include<vector>
    using namespace std;
    #define REG register
    #define REP(i,x,y) for(REG int i=x;i<=y;i++)
    #define UP(i,x,y) for(REG int i=x;i>=y;i--)
    #define IN inline
    #define ll long long
    #define mm(x) memset(x,0,sizeof(x))
     
    ll A[10],B[10],f[20][11],a[20],p[10];
     
    ll dfs(ll* A,ll h,bool limit,bool lead){
        if(h==0) return 1;
        if(!limit  && !lead && f[h][10]!=-1){
            REP(i,0,9) A[i]+=f[h][i];
            return f[h][10];
        }
        int end=limit?a[h]:9;
        ll ans=0;
        REP(i,0,end){
            if(limit && i==end){
                p[i ]=dfs(A,h-1,1,lead&&i==0);
                A[i]+=p[i];
                ans+=p[i];
            }
            else{
                p[i]=dfs(A,h-1,0,lead&&i==0);
                A[i]+=p[i];
                ans+=p[i];
            }
            if(lead && i==0)
                A[i]-=p[i];
        }
        if(!limit && !lead){
            REP(i,0,9) f[h][i]=f[h-1][i]*10+p[i];
            f[h][10]=ans;
        }
        return ans;
    }
     
    void solve(ll* A,ll x){
        ll n=0;
        if(x==0){return;}
        while(x>0){
            a[++n]=x%10;
            x/=10;
        }
        dfs(A,n,1,1);
    }
     
    int main(){
        ll l,r;
        scanf("%lld %lld",&l,&r);
        REP(i,0,20) f[i][10]=-1;
        solve(B,l-1);solve(A,r);
        REP(i,0,8) printf("%lld ",A[i]-B[i]);
        printf("%lld
    ",A[9]-B[9]);
         
        return 0;
    }
    View Code

    2、dp+递归

    #include<iostream>
    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #include<cmath>
    #include<queue>
    #include<vector>
    using namespace std;
    #define REG register
    #define REP(i,x,y) for(REG int i=x;i<=y;i++)
    #define UP(i,x,y) for(REG int i=x;i>=y;i--)
    #define IN inline
    #define ll long long
    #define mm(x) memset(x,0,sizeof(x))
     
    const int maxn=16,maxm=20;
    struct sw{
        ll a[10];
    };
    
    int a[maxm];
    ll A,B,fac[maxm];
    sw f[maxn][maxn];
    
    sw operator +(sw a,sw b){
        sw c;
        REP(i,0,9) c.a[i]=a.a[i]+b.a[i];
        return c;
    }
    
    sw dp(ll num){
        int len=0;
        sw ans;
        REP(i,0,9) ans.a[i]=0;
        ans.a[0]=1;
        if(!num) return ans;
        ll number=num;
        while(num){
            a[++len]=num%10;
            num/=10;
        }
        REP(i,1,len-1){
            REP(j,1,9)
                ans=ans+f[i][j];
        }    
        UP(i,len,1){
            for(int j=(i==len);j<a[i];j++){
                ans=ans+f[i][j];
            }
            number%=fac[i-1];
            ans.a[a[i]]+=number+1;
        }
        //for(int k=0;k<=9;k++)printf("%d ",ans.a[k]);puts("");
        return ans;
    }
    
    int main(){
        fac[0]=1;
        REP(i,1,maxn) fac[i]=fac[i-1]*10;
        REP(j,0,9) f[1][j].a[j]=1;
        REP(i,2,maxn){
            REP(j,0,9){
                REP(k,0,9){
                    f[i][j]=f[i][j]+f[i-1][k];
                    f[i][j].a[j]+=fac[i-2];
                }
                //printf("[%d][%d]",i,j);
                //for(int k=0;k<=9;k++)printf("%d ",f[i][j].a[k]);
                //puts("");
            }
        }
        scanf("%lld %lld",&A,&B);
        sw cA=dp(A-1),cB=dp(B);
        REP(i,0,8) printf("%lld ",cB.a[i]-cA.a[i]);
        printf("%lld
    ",cB.a[9]-cA.a[9]);
        
        return 0;
    }
    View Code

    高位限制 imit&&i==a[h]
    前导零  lead&&i==0
    数位   h=h-1(第0位是个位,第-1位直接返回)
    前缀状态 state(表示(h,len]的状态)

    tips:

    1、减法的艺术:

    记忆化前缀和为sum
    对后面还有all-sum需求时满足条件的个数

    2、计数转求和:

    求解满足条件的数字的和

    对于每一位符合条件的i,即sum+=i*10^pos*cnt(cnt为数字个数),同时记忆化数字个数num和总和sum即可

    求第n个数字,预处理f[i]表示长度为i的数字(不含前导0)的个数,然后从最高位开始按位确定

    例题:

    数字1的数量(nod51 1009

    数字计数(bzoj 1833

    windy数(bzoj 1026

    self同类分布(bzoj 1799

    储能表(bzoj 4513

  • 相关阅读:
    WinAPI: SetRect 及初始化矩形的几种办法
    [书目20080225]软件工程与项目管理解析
    [转]npkcrypt 服务启动失败
    CPU是为用户服务的
    Web.config基础
    VBScript学习
    [书目20080130]如何成功管理一个软件项目
    [转]DB2常用命令大全
    [转]C++语法概括及其示例(示例代码下载)
    用友华表Cell产生柱状图表
  • 原文地址:https://www.cnblogs.com/EvfX/p/8763279.html
Copyright © 2020-2023  润新知