zoukankan      html  css  js  c++  java
  • 数位DP之小小结

    资料链接:http://wenku.baidu.com/view/9de41d51168884868662d623.html

                 http://wenku.baidu.com/view/d2414ffe04a1b0717fd5dda8.html

    几位大大的数位BLOG:http://www.cnblogs.com/jackge/archive/2013/05/15/3080958.html 

                                 http://www.cnblogs.com/kuangbin/category/476047.html 

                                 CXLOVE:http://blog.csdn.net/acm_cxlove/article/details/7819907 

    给出数位DP的DFS记忆化DP模板;

    因为原理比较简单,不用去构造方程,而且简洁。。

    ll dfs(int pos,int cet,int sum,int flag){
       if (sum<0return 0;
       if (pos<=0return sum==0;
       if (!flag&&dp[pos][cet][sum]!=-1return dp[pos][cet][sum];
           int end=flag?a[pos]:9;
       ll ret=0;
       for (int i=0;i<=end;i++)
       ret+=dfs(pos-1,cet,sum+i*(pos-cet),flag&&(end==i));
       if (!flag) dp[pos][cet][sum]=ret;
       return ret;

    } 

    复杂度是DP方程的状态数,DP设计的好坏与DP的状态数有关。。

    FLAG表示是否在边界,当不再边界时我们可以做无关后面数的操作,否则,要考虑后面的数对前面有没有影响。。 

    我做的几道题:

    HDU 3652:

     1 #include<stdio.h>
     2 #include<string.h>
     3 #include<algorithm>
     4 #include<math.h>
     5 #include<iostream>
     6 using namespace std;
     7 int a[123];
     8 int len;
     9 int dp[11][90][90][10];
    10 
    11 
    12 int dfs(int pos,int sum,int res,int last,int flag)
    13 {
    14     if (pos<0) return res&&(sum==0);
    15     if (!flag&&dp[pos][sum][res][last]!=-1) return dp[pos][sum][res][last];
    16     int end=flag?a[pos]:9;
    17     int ret=0;
    18     for (int i=0;i<=end;i++)
    19     ret+=dfs(pos-1,(sum*10+i)%13,res||(last==1&&i==3),i,flag&&i==end);
    20     if (!flag) dp[pos][sum][res][last]=ret;
    21     return ret;
    22 }
    23 
    24 int get(int x)
    25 {
    26     len=0;
    27     while (x){
    28             a[len++]=x%10;
    29             x/=10;
    30     }
    31     return dfs(len-1,0,0,0,1);
    32 }
    33 
    34 int main()
    35 {
    36     int n;
    37     memset(dp,-1,sizeof(dp));
    38     while (scanf("%d",&n)!=EOF)
    39     {
    40         printf("%d
    ",get(n));
    41     }
    42 }
    View Code

     HDU  3555:   

     1 #include<stdio.h>
     2 #include<string.h>
     3 #include<algorithm>
     4 #include<math.h>
     5 #include<iostream>
     6 using namespace std;
     7 typedef long long ll;
     8 int a[123];
     9 int len;
    10 ll dp[60][90][10];
    11 
    12 
    13 ll dfs(int pos,int res,int last,int flag)
    14 {
    15     if (pos<0) return res;
    16     if (!flag&&dp[pos][res][last]!=-1) return dp[pos][res][last];
    17     int end=flag?a[pos]:9;
    18     ll ret=0;
    19     for (int i=0;i<=end;i++)
    20     ret+=dfs(pos-1,res||(last==4&&i==9),i,flag&&i==end);
    21     if (!flag) dp[pos][res][last]=ret;
    22     return ret;
    23 }
    24 
    25 ll get(ll x)
    26 {
    27     len=0;
    28     while (x){
    29             a[len++]=x%10;
    30             x/=10;
    31     }
    32     return dfs(len-1,0,0,1);
    33 }
    34 
    35 int main()
    36 {
    37     ll n;
    38     int T;
    39     scanf("%d",&T);
    40     memset(dp,-1,sizeof(dp));
    41     while (T--)
    42     {
    43         scanf("%I64d",&n);
    44         printf("%I64d
    ",get(n));
    45     }
    46 }
    View Code

                  

     HDU 4389:有打表方法;每个100000个数打表,再统计;

                                  数位DP代码:

     1 #include<stdio.h>
     2 #include<string.h>
     3 #include<algorithm>
     4 #include<math.h>
     5 typedef long long ll;
     6 #define mod 2520
     7 int inx[22456];
     8 ll dp[20][50][2522];
     9 int a[1234];
    10 using namespace std;
    11 
    12 void init(){
    13     int num=0;
    14     for (int i=1;i<=mod;i++)
    15     if (mod%i==0) inx[i]=++num;
    16 }
    17 
    18 int gcd(int a,int b){
    19     if (a%b==0) return b;
    20     return gcd(b,a%b);
    21 }
    22 
    23 int lcm(int a,int b){
    24     return a*b/gcd(a,b);
    25 }
    26 
    27 ll dfs(int pos,int slcm,int sum,int flag){
    28    if (pos<0) return sum%slcm==0;
    29    if (!flag&&dp[pos][inx[slcm]][sum]!=-1) return dp[pos][inx[slcm]][sum];
    30    int end=flag?a[pos]:9;
    31    ll ret=0;
    32    for (int i=0;i<=end;i++){
    33    int nowlcm=slcm;
    34    int nowsum=(sum*10+i) %mod;
    35    if (i) nowlcm=lcm(nowlcm,i);
    36    ret+=dfs(pos-1,nowlcm,nowsum,flag&&i==end);
    37    }
    38    if (!flag) dp[pos][inx[slcm]][sum]=ret;
    39    return ret;
    40 }
    41 
    42 ll get(ll x)
    43 {
    44     int pos=0;
    45     while (x)
    46     {
    47      a[pos++]=x%10;
    48      x/=10;
    49     }
    50     return dfs(pos-1,1,0,1);
    51 }
    52 
    53 int main(){
    54     int T;
    55     scanf("%d",&T);
    56     memset(dp,-1,sizeof(dp));
    57     init();
    58     while (T--){
    59         ll a,b;
    60         scanf("%I64d%I64d",&a,&b);
    61         printf("%I64d
    ",get(b)-get(a-1));
    62     }
    63     return 0;
    64 }
    View Code

     

                                  这里我们不可能保存每个值去除以其数位的和,我们可以枚举数位的和,然后DFS能满足的数的个数,再看其数能不能整除其数位。

                                 数位和的状态是1-81。。。;

                 

     HDU 4334 : 

     1 #include<stdio.h>
     2 #include<algorithm>
     3 #include<math.h>
     4 #include<string.h>
     5 #include<algorithm>
     6 
     7 using namespace std;
     8 int bit[234];
     9 int dp[20480][20];
    10 
    11 int dfs(int t,int num,int flag)
    12 {
    13     if (t==0) return num>=0;
    14     if (num<0) return 0;
    15     if (!flag&&dp[num][t]!=-1) return dp[num][t];
    16     int ans=0;
    17     int  end=flag?bit[t]:9;
    18     for (int i=0;i<=end;i++)
    19          ans+=dfs(t-1,num-i*(1<<(t-1)),flag&&i==end);
    20     if (!flag) dp[num][t]=ans;
    21     return ans;
    22 }
    23 int main()
    24 {
    25     int T;
    26     scanf("%d",&T);
    27     memset(dp,-1,sizeof(dp));
    28     for (int i=1;i<=T;i++)
    29     {
    30         int a,b;
    31         scanf("%d%d",&a,&b);
    32         int t=0;
    33         int  ans=0;
    34         while (a)
    35         {
    36             ans+=a%10*(1<<t);
    37             t++;
    38             a/=10;
    39         }
    40         int len=0;
    41         while (b)
    42         {
    43             bit[++len]=b%10;
    44             b/=10;
    45         }
    46         printf("Case #%d: %d
    ",i,dfs(len,ans,1));
    47     }
    48     return 0;
    49 }
    View Code

                      前面的做了差不多,这也是简单题了,先预处理F[A],然后处理。。

    codeforces 55D: http://codeforces.com/problemset/problem/55/D

    比较麻烦的题;求一个范围的书能被其数位整除的个数。

                       切题点:1-9的公倍数为2520; 

    我们可以定义方程DP[POS][LCM][SUM]表示:处理第POS位,其数位的最小公倍数数多少,该数MOD2520是多少,但是有个问题会爆内存,

    要开DP[20][2520][2520];

    解决办法是:2520=2^3*3^2*5*7

    y于是又4*3*2*2= 48中组合。。

    我们可以对LCM用下标代替,就不会爆内存了。。。真是奇妙的方法。。

     1 #include<stdio.h>
     2 #include<string.h>
     3 #include<algorithm>
     4 #include<math.h>
     5 typedef long long ll;
     6 #define mod 2520
     7 int inx[22456];
     8 ll dp[20][50][2522];
     9 int a[1234];
    10 using namespace std;
    11 
    12 void init(){
    13     int num=0;
    14     for (int i=1;i<=mod;i++)
    15     if (mod%i==0) inx[i]=++num;
    16 }
    17 
    18 int gcd(int a,int b){
    19     if (a%b==0) return b;
    20     return gcd(b,a%b);
    21 }
    22 
    23 int lcm(int a,int b){
    24     return a*b/gcd(a,b);
    25 }
    26 
    27 ll dfs(int pos,int slcm,int sum,int flag){
    28    if (pos<0) return sum%slcm==0;
    29    if (!flag&&dp[pos][inx[slcm]][sum]!=-1) return dp[pos][inx[slcm]][sum];
    30    int end=flag?a[pos]:9;
    31    ll ret=0;
    32    for (int i=0;i<=end;i++){
    33    int nowlcm=slcm;
    34    int nowsum=(sum*10+i) %mod;
    35    if (i) nowlcm=lcm(nowlcm,i);
    36    ret+=dfs(pos-1,nowlcm,nowsum,flag&&i==end);
    37    }
    38    if (!flag) dp[pos][inx[slcm]][sum]=ret;
    39    return ret;
    40 }
    41 
    42 ll get(ll x)
    43 {
    44     int pos=0;
    45     while (x)
    46     {
    47      a[pos++]=x%10;
    48      x/=10;
    49     }
    50     return dfs(pos-1,1,0,1);
    51 }
    52 
    53 int main(){
    54     int T;
    55     scanf("%d",&T);
    56     memset(dp,-1,sizeof(dp));
    57     init();
    58     while (T--){
    59         ll a,b;
    60         scanf("%I64d%I64d",&a,&b);
    61         printf("%I64d
    ",get(b)-get(a-1));
    62     }
    63     return 0;
    64 }
    View Code

    FZU 2113:

    OJ挂了,现在也没评测出来。。

    求一个范围[L,R]数中1的总数。。

    方程想到就是超级大水题;

    #include<stdio.h>
    #include<iostream>
    #include<string.h>
    #include<cmath>
    typedef long long ll;
    int a[123];
    ll dp[123][123];
    using namespace std;
    
    
    ll dfs(int pos,int num,int flag)
    {
        if (pos<0) return num;
        if (!flag&&dp[pos][num]!=-1) return dp[pos][num];
        int end=flag?a[pos]:9;
        ll ret=0;
        for (int i=0;i<=end;i++)
            if (i==1) ret+=dfs(pos-1,num+1,flag&&end==i);
            else ret+=dfs(pos-1,num,flag&&end==i);
        if (!flag) dp[pos][num]=ret;
        return ret;
    }
    
    ll get(ll x)
    {
        int pos=0;
        while (x){
            a[pos++]=x%10;
            x/=10;
        }
        return dfs(pos,0,1);
    }
    
    int main()
    {
        ll a,b;
        memset(dp,-1,sizeof(dp));
        while (scanf("%lld%lld",&a,&b)!=EOF){
              printf("%lld
    ",get(b)-get(a-1));
        }
        return 0;
    }
    View Code

     还有好多好多各种状态的数位DP题,果然太弱切不动。。

       

  • 相关阅读:
    190401装饰器-高阶函数-闭包
    190329迭代器-生成器-三元运算-列表解析
    OpenStack-Mitaka
    RabbitMQ
    190328文件处理
    190322函数
    190322字符串
    190321集合
    190320运算符&数据类型
    Zookeeper
  • 原文地址:https://www.cnblogs.com/forgot93/p/3889950.html
Copyright © 2011-2022 走看看