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

    强烈推荐https://blog.csdn.net/wust_zzwh/article/details/52100392,写的真的太棒了Orz,(所以下面的都引自这篇博客咳咳

    一、模板

    数位dp是一种计数用的dp,一般就是要统计一个区间[le,ri]内满足一些条件数的个数。所谓数位dp,是在数位上进行dp,实质就是换一种暴力枚举的方式,使得新的枚举方式满足dp的性质,然后记忆化就可以了。

    如果是暴力枚举:

    for(int i=le;i<=ri;i++)
            if(right(i)) ans++;

    而数位dp的话,是控制上界枚举,从最高位开始往下枚举,例如:ri=213,那么我们从百位开始枚举:百位可能的情况有0,1,2(觉得这里枚举0有问题的继续看)

    然后每一位枚举都不能让枚举的这个数超过上界213(下界就是0或者1,这个次要),当百位枚举了1,那么十位枚举就是从0到9,因为百位1已经比上界2小了,后面数位枚举什么都不可能超过上界。所以问题就在于:当高位枚举刚好达到上界是,那么紧接着的一位枚举就有上界限制了。具体的这里如果百位枚举了2,那么十位的枚举情况就是0到1,如果前两位枚举了21,最后一位之是0到3(这一点正好对于代码模板里的一个变量limit 专门用来判断枚举范围)。最后一个问题:最高位枚举0:百位枚举0,相当于此时我枚举的这个数最多是两位数,如果十位继续枚举0,那么我枚举的就是个位数咯,因为我们要枚举的是小于等于ri的所以数,当然不能少了位数比ri小的咯!(这样枚举是为了无遗漏的枚举,不过可能会带来一个问题,就是前导零的问题,模板里用lead变量表示,不过这个不是每个题目都是会有影响的,可能前导零不会影响我们计数,具体要看题目)

    然后是大神的模板

    typedef long long ll;
    int a[20];
    ll dp[20][state];//不同题目状态不同
    ll dfs(int pos,/*state变量*/,bool lead/*前导零*/,bool limit/*数位上界变量*/)//不是每个题都要判断前导零
    {
        //递归边界,既然是按位枚举,最低位是0,那么pos==-1说明这个数我枚举完了
        if(pos==-1) return 1;/*这里一般返回1,表示你枚举的这个数是合法的,那么这里就需要你在枚举时必须每一位都要满足题目条件,也就是说当前枚举到pos位,一定要保证前面已经枚举的数位是合法的。不过具体题目不同或者写法不同的话不一定要返回1 */
        //第二个就是记忆化(在此前可能不同题目还能有一些剪枝)
        if(!limit && !lead && dp[pos][state]!=-1) return dp[pos][state];
        /*常规写法都是在没有限制的条件记忆化,这里与下面记录状态是对应,具体为什么是有条件的记忆化后面会讲*/
        int up=limit?a[pos]:9;//根据limit判断枚举的上界up;这个的例子前面用213讲过了
        ll ans=0;
        //开始计数
        for(int i=0;i<=up;i++)//枚举,然后把不同情况的个数加到ans就可以了
        {
            if() ...
            else if()...
            ans+=dfs(pos-1,/*状态转移*/,lead && i==0,limit && i==a[pos]) //最后两个变量传参都是这样写的
            /*这里还算比较灵活,不过做几个题就觉得这里也是套路了
            大概就是说,我当前数位枚举的数是i,然后根据题目的约束条件分类讨论
            去计算不同情况下的个数,还有要根据state变量来保证i的合法性,比如题目
            要求数位上不能有62连续出现,那么就是state就是要保存前一位pre,然后分类,
            前一位如果是6那么这意味就不能是2,这里一定要保存枚举的这个数是合法*/
        }
        //计算完,记录状态
        if(!limit && !lead) dp[pos][state]=ans;
        /*这里对应上面的记忆化,在一定条件下时记录,保证一致性,当然如果约束条件不需要考虑lead,这里就是lead就完全不用考虑了*/
        return ans;
    }
    ll solve(ll x)
    {
        int pos=0;
        while(x)//把数位都分解出来
        {
            a[pos++]=x%10;//个人老是喜欢编号为[0,pos),看不惯的就按自己习惯来,反正注意数位边界就行
            x/=10;
        }
        return dfs(pos-1/*从最高位开始枚举*/,/*一系列状态 */,true,true);//刚开始最高位都是有限制并且有前导零的,显然比最高位还要高的一位视为0嘛
    }
    int main()
    {
        ll le,ri;
        while(~scanf("%lld%lld",&le,&ri))
        {
            //初始化dp数组为-1,这里还有更加优美的优化,后面讲
            printf("%lld
    ",solve(ri)-solve(le-1));
        }
    }

    然后有些优化什么的还是去看原博客吧qwq

    二、例题

    1.hdu 2089 不要62

    不要连续的62,所以只需要记录上一位是不是6就可以了,f[pos][sta]表示第pos位,上一位是不是6

    #include <bits/stdc++.h>
    #define eps 0.000001
    using namespace std;
    int l,r;
    int f[20][2],a[20];
    int dfs(int pos,int pre,int sta,bool limit)
    {
        if (pos==-1) return 1;
        if (!limit && f[pos][sta]!=-1) return f[pos][sta];
        int up;
        if (limit) up=a[pos]; else up=9;
    
        int tmp=0;
        for (int i=0;i<=up;i++)
        {
            if (pre==6&&i==2|| i==4) continue;
            tmp+=dfs(pos-1,i,i==6,limit&&i==a[pos]);
        }
        if (!limit) f[pos][sta]=tmp;
        return tmp;
    }
    int solve(int x)
    {
        int pos=0;
        while (x)
        {
            a[pos++]=x%10;
            x/=10;
        }
        return dfs(pos-1,-1,0,1);
    }
    int main()
    {
        while (scanf("%d%d",&l,&r)&&l+r)
        {
            memset(f,-1,sizeof(f));
            printf("%d
    ",solve(r)-solve(l-1));
        }
        return 0;
    }
    View Code

    2.hdu 4734 F(x)

    #include <bits/stdc++.h>
    #define eps 0.000001
    using namespace std;
    const int N=1e4+50;
    int T,r,all,A;
    int f[20][N],a[20];
    int F(int x)
    {
        if (x==0) return 0;
        int ans=F(x/10);
        return ans*2+(x%10);
    }
    int dfs(int pos,int sum,bool limit)
    {
        if (pos==-1) return sum<=all;
        if (sum>all) return 0;
        if (!limit && f[pos][all-sum]!=-1) return f[pos][all-sum];
        int up= limit? a[pos] :9;
    
        int tmp=0;
        for (int i=0;i<=up;i++)
        {
            tmp+=dfs(pos-1,sum+i*(1<<pos),limit&&i==a[pos]);
        }
        if (!limit) f[pos][all-sum]=tmp;
        return tmp;
    }
    int solve(int x)
    {
        int pos=0;
        while (x)
        {
            a[pos++]=x%10;
            x/=10;
        }
        return dfs(pos-1,0,1);
    }
    int main()
    {
        scanf("%d",&T);
        memset(f,-1,sizeof(f));
        for (int ca=1;ca<=T;ca++)
        {
            scanf("%d%d",&A,&r);
            all=F(A);
            printf("Case #%d: %d
    ",ca,solve(r));
        }
        return 0;
    }
    View Code

    3.poj 3252 Round Numbers

    二进制中0的数量要不少于1的数量的数的个数,f[pos][num],到当前数位pos,0的数量减去1的数量不少于num的方案数,一个简单的问题,中间某个pos位上num可能为负数(这不一定是非法的,因为我还没枚举完嘛,只要最终的num>=0j就可以),因为最小就-32吧,直接加上32,把32当0用。这题主要是要想讲一下lead的用法,显然我要统计0的数量,前导零是有影响的,所以!lead&&!limit才能dp。

    #include <iostream>
    #include <cstdio>
    #include <cstdlib>
    #include <cstring>
    #include <algorithm>
    //#include <bits/stdc++.h>
    #define eps 0.000001
    using namespace std;
    const int N=1e4+50;
    int l,r;
    int f[35][70],a[35];
    int dfs(int pos,int sta,bool lead,bool limit)
    {
        if (pos==-1) return sta>=32;
        if (!lead && !limit && f[pos][sta]!=-1) return f[pos][sta];
        int up= limit? a[pos] :1;
    
        int tmp=0;
        for (int i=0;i<=up;i++)
        {
            if (lead && i==0) tmp+=dfs(pos-1,sta,lead,limit && i==a[pos]);
            else tmp+=dfs(pos-1,sta+(i==0?1:-1),lead && i==0,limit && i==a[pos]);
        }
        if (!lead && !limit) f[pos][sta]=tmp;
        return tmp;
    }
    int solve(int x)
    {
        int pos=0;
        while (x)
        {
            a[pos++]=x&1;
            x>>=1;
        }
        return dfs(pos-1,32,1,1);
    }
    int main()
    {
        memset(f,-1,sizeof(f));
        scanf("%d%d",&l,&r);
        printf("%d
    ",solve(r)-solve(l-1));
        return 0;
    }
    View Code

    4.bzoj 1799 self同类分布

    一个数是它自己数位和的倍数。

    枚举数位和,因为总就162,然后问题就变成了一个数%mod=0,mod是枚举的,想想状态:dp[pos][sum][val],当前pos位上数位和是sum,val就是在算这个数%mod,(从高位算  *10+i),因为我们枚举的数要保证数位和等于mod,还要保证这个数是mod的倍数,很自然就能找到这些状态,显然对于每一个mod,val不能保证状态唯一,所以直接对每一个mod,memset一次。

    #include <iostream>
    #include <cstdio>
    #include <cstdlib>
    #include <cstring>
    #include <algorithm>
    //#include <bits/stdc++.h>
    #define eps 0.000001
    #define ll long long
    using namespace std;
    const int N=1e4+50;
    int T;
    ll l,r;
    ll f[20][170][170];
    int a[20];
    ll dfs(int pos,int sum,int val,int mod,bool limit)
    {
        if (sum-9*pos-9>0) return 0;
        if (pos==-1) return sum==0 && val==0;
        if (!limit && f[pos][sum][val]!=-1) return f[pos][sum][val];
        int up= limit? a[pos] :9;
     
        ll tmp=0;
        for (int i=0;i<=up;i++)
        {
            if (sum-i<0) break;
            tmp+=dfs(pos-1,sum-i,(val*10+i)%mod,mod,limit && i==a[pos]);
        }
        if (!limit) f[pos][sum][val]=tmp;
        return tmp;
    }
    ll solve(ll x)
    {
        int pos=0;
        while (x)
        {
            a[pos++]=x%10;
            x/=10;
        }
     
        ll ans=0;
        for (int i=1;i<=pos*9;i++)
        {
            memset(f,-1,sizeof(f));
            ll tmp=dfs(pos-1,i,0,i,1);
            ans+=tmp;
        }
        return ans;
    }
    int main()
    {
      /*  scanf("%d",&T);
        for (int ca=1;ca<=T;ca++)
        {
            scanf("%lld",&r);
            printf("Case %d: %lld
    ",ca,solve(r));
        }*/
        scanf("%lld%lld",&l,&r);
        printf("%lld
    ",solve(r)-solve(l-1));
        return 0;
    }
    View Code
  • 相关阅读:
    Android java.lang.UnsupportedClassVersionError: com/android/dx/command/Main : Unsupported major.minor ver
    Android EditText光标位置(定位到最后)
    Android EditText获取光标位置并插入字符删除字符
    Android 仿微信小视频录制
    Android仿微信小视频录制功能
    Android消息机制之实现两个不同线程之间相互传递数据相互调用
    Android Data Binding 技术
    Android中解析XML
    Android 怎样把光标放在EditText中文本的末尾处?
    Hadoop HBase概念学习系列之RowKey设计(二十九)
  • 原文地址:https://www.cnblogs.com/tetew/p/9435832.html
Copyright © 2011-2022 走看看