• 算法复习——数位dp


    开头由于不知道讲啥依然搬讲义

    对于引入的这个问题,讲义里已经很清楚了,我更喜欢用那个建树的理解····

    相当于先预处理f,然后从起点开始在树上走··记录目前已经找到了多少个满足题意的数k,如果枚举到第i位,下一位要走的是1,需要加上左子树的总数f[i-1][K-k],如果下一位走的是0直接走左子树即可····

    #include<iostream>
    #include<cstdio>
    #include<cstdlib>
    #include<cmath>
    #include<ctime>
    #include<cctype>
    #include<cstring>
    #include<string>
    #include<algorithm>
    using namespace std;
    const int N=32;
    int f[N][N],x,y,k,b,num[N],n,ans1,ans2;
    inline void pre()
    {
      for(int i=0;i<=31;i++)
      {
        f[i][0]=f[i][i]=1;
        for(int j=1;j<i;j++)
          f[i][j]=f[i-1][j]+f[i-1][j-1];
      }
    }
    inline void trans(int x)
    {
      int temp=x;n=0;
      while(temp)  num[++n]=temp%b,temp/=b;
    }
    inline int solve()
    {
      int tot=0,sum=0,i;
      for(i=n;i>=1;i--)
      {
        if(num[i]>1)   //大于1的话此时整个子树代表的数都是小于x的,因此直接统计答案 
        {
          sum+=f[i][k-tot];  break;
        }
        else if(num[i]==1)
        {
          sum+=f[i-1][k-tot];   //等于1统计左儿子的答案(即小于该数且又有k个1的数的个数) 
          if(++tot>k)  break;   
        }
      }
      if(i!=1&&tot==k)  sum++;  //如果该数本身就符合答案的话答案加1 
      return sum;
    }
    int main()
    {
      //freopen("a.in","r",stdin);
      pre();
      scanf("%d%d%d%d",&x,&y,&k,&b);
      trans(y),ans1=solve();
      trans(x-1),ans2=solve();
      cout<<ans1-ans2<<endl;
      return 0;
    }

    例题:

    1.windy数(bzoj1026)

    Description

      windy定义了一种windy数。不含前导零且相邻两个数字之差至少为2的正整数被称为windy数。 windy想知道,
    在A和B之间,包括A和B,总共有多少个windy数?

    Input

      包含两个整数,A B。

    Output

      一个整数

    Sample Input

    【输入样例一】
    1 10
    【输入样例二】
    25 50

    Sample Output

    【输出样例一】
    9
    【输出样例二】
    20

    HINT

    【数据规模和约定】

    100%的数据,满足 1 <= A <= B <= 2000000000 。

    Source

    首先运用数位dp的常规思想,用ans(B)-ans(A-1);

    这道题我们用f[i][j][0/1]表示第j位为i且小于/大于原数前i为满足题意的数的个数,然后枚举上一位进行dp即可·····具体看代码··

    #include<iostream>
    #include<cstdio>
    #include<cstdlib>
    #include<cmath>
    #include<ctime>
    #include<cctype>
    #include<cstring>
    #include<string>
    #include<algorithm>
    using namespace std;
    const int N=15;
    int f[N][N][2],num[N],n,a,b;
    inline int Abs(int a)
    {
      return a<0?-a:a;
    }
    inline int solve(int x)
    {
      if(!x)  return 0;
      n=0;memset(f,0,sizeof(f));int ans=0;
      while(x)  num[++n]=x%10,x/=10;
      for(int i=0;i<=9;i++)
        if(i<=num[1])  f[i][1][0]=1;
        else f[i][1][1]=1;
      for(int i=2;i<=n;i++)
        for(int j=0;j<=9;j++)
          for(int k=0;k<=9;k++)
            if(Abs(j-k)>=2)
            {  
              if(j<num[i])  f[j][i][0]+=f[k][i-1][1]+f[k][i-1][0];  
              else if(j==num[i])  f[j][i][0]+=f[k][i-1][0],f[j][i][1]+=f[k][i-1][1];
              else f[j][i][1]+=f[k][i-1][1]+f[k][i-1][0];
            }   
      for(int i=1;i<=9;i++)
      {
        ans+=f[i][n][0];
        if(i==num[n])  break;
      }
      for(int i=n-1;i>=1;i--)
        for(int j=1;j<=9;j++)
          ans+=f[j][i][0]+f[j][i][1];
      return ans;
    }
    int main()
    {
      //freopen("a.in","r",stdin);
      scanf("%d%d",&a,&b);
      printf("%d
    ",solve(b)-solve(a-1));
      return 0;
    }

     2.不要62(hdu2089)

    Problem Description

    杭州人称那些傻乎乎粘嗒嗒的人为62(音:laoer)。
    杭州交通管理局经常会扩充一些的士车牌照,新近出来一个好消息,以后上牌照,不再含有不吉利的数字了,这样一来,就可以消除个别的士司机和乘客的心理障碍,更安全地服务大众。
    不吉利的数字为所有含有4或62的号码。例如:
    62315 73418 88914
    都属于不吉利号码。但是,61152虽然含有6和2,但不是62连号,所以不属于不吉利数字之列。
    你的任务是,对于每次给出的一个牌照区间号,推断出交管局今次又要实际上给多少辆新的士车上牌照了。

    Input

    输入的都是整数对n、m(0<n≤m<1000000),如果遇到都是0的整数对,则输入结束。

    Output

    对于每个整数对,输出一个不含有不吉利数字的统计个数,该数值占一行位置。

    Sample Input

    1 100 0 0

    Sample Output

    80
     
    来一道非常经典的数位dp,这道题也包含了许多数位dp的常用方法:拆位+记忆化搜索+判断封顶
    具体的就不多说了···直接看代码吧
    #include<iostream>
    #include<cstdio>
    #include<cstdlib>
    #include<cmath>
    #include<ctime>
    #include<cctype>
    #include<cstring>
    #include<string>
    #include<algorithm>
    using namespace std;
    const int N=15;
    int f[N][N][2],a,b,num[N],n,ans1,ans2;
    inline int R()
    {
      char c;int f=0;
      for(c=getchar();c<'0'||c>'9';c=getchar());
      for(;c<='9'&&c>='0';c=getchar())
        f=(f<<3)+(f<<1)+c-'0';
      return f;
    }
    inline void trans(int x)
    {
      memset(f,-1,sizeof(f));
      int temp=x;n=0;
      while(temp)  n++,temp/=10;
      temp=x;
      for(int i=n;i;i--)
        num[i]=temp%10,temp/=10;
    }
    inline int dfs(int pos,int pre,bool jud)
    {
      if(pos==n+1)  return 1;
      if(f[pos][pre][jud]!=-1)  return f[pos][pre][jud];
      int maxx=jud?num[pos]:9,ans=0;
      for(int i=0;i<=maxx;i++)
      {
        if(i==4||(pre==6&&i==2))  continue;
        ans+=dfs(pos+1,i,jud&&i==maxx);
      }
      return f[pos][pre][jud]=ans;
    }
    int main()
    {
      while(true)
      {  
        a=R(),b=R();
        if(a==0&&b==0)  break;
        trans(a-1);ans1=dfs(1,0,1);
        trans(b);ans2=dfs(1,0,1);
        cout<<ans2-ans1<<endl;
      }
      return 0;
    }

    3.B-number(hdu3652)

    Problem Description

    A wqb-number, or B-number for short, is a non-negative integer whose decimal form contains the sub- string "13" and can be divided by 13. For example, 130 and 2613 are wqb-numbers, but 143 and 2639 are not. Your task is to calculate how many wqb-numbers from 1 to n for a given integer n.
    Input
    Process till EOF. In each line, there is one positive integer n(1 <= n <= 1000000000).

    Output

    Print each answer in a single line.

    Sample Input

    13 100 200 1000

    Sample Output

    1 1 2 2

    和上道题很像啊···只是需要在dfs时多加入一个mod和一个jud来判断是否整除和是否找到13即可·······

    代码:

    #include<iostream>
    #include<cstdio>
    #include<cstdlib>
    #include<cmath>
    #include<ctime>
    #include<cctype>
    #include<cstring>
    #include<string>
    #include<algorithm>
    using namespace std;
    const int N=15;
    int f[N][N][2][2][N],num[N],n,a;
    inline void trans(int x)
    {
      memset(f,-1,sizeof(f));
      int temp=x;n=0;
      while(temp)  temp/=10,n++;
      temp=x;
      for(int i=n;i>=1;i--)  num[i]=temp%10,temp/=10;
    } 
    inline int dfs(int pos,int mod,int jud,int lim,int pre)
    {
      if(pos==n+1)  return jud&&!mod;
      if(f[pos][mod][jud][lim][pre]!=-1)  return f[pos][mod][jud][lim][pre];
      int maxx=lim?num[pos]:9,ans=0,vmod,vjud;
      for(int i=0;i<=maxx;i++) 
      {
        vmod=(mod*10+i)%13;vjud=jud;
        if(pre==1&&i==3)  vjud=1;
        ans+=dfs(pos+1,vmod,vjud,lim&&i==maxx,i);
      }
      return f[pos][mod][jud][lim][pre]=ans;
    }
    int main()
    {
      //freopen("a.in","r",stdin);
      while(scanf("%d",&a)!=EOF)
      {
        trans(a);
        printf("%d
    ",dfs(1,0,0,1,0));
      }
      return 0;
    }

     4.beautiful number(codeforces 55d)

    题目描述

    如果一个数能够被其每个除0的数位的数都整除,那么这个数就叫做美丽数。
    给定一个区间 [x,y] ,计算该区间内有多少个美丽数。

    输入格式

    输入文件中有一行,为空格隔开的两个正整数 x 和 y(1≤x≤y≤9*1018)。

    输出格式

    输出一个整数,即区间 [x,y] 内的美丽数个数。

    样例数据 1

    输入 

    1 9

    输出

    9

    样例数据 2

    输入  

    12 15

    输出

    2
     
    这道题首先先到的是用四维dp,第一维记录位数,第二维记录之前数的公倍数,第三维记录目前凑成的数的数字之和,最后一维记录是否封顶····
    然而第二维和第三维是在太大···直接记录会超空间···
    第三维其实是很好优化的···因为1到9的公倍数只有2520,因此每次之和模掉一个2520就可以了··
    至于第二维···由于1到9各个数字组成的数的公倍数最多只有48个·····因此可以利用hash表来储存····(其实也不叫hash表··只是一个普通数组而已)
    然后就可以了···另外注意要开long long啊···
    #include<iostream>
    #include<cstdio>
    #include<cstdlib>
    #include<cmath>
    #include<ctime>
    #include<cctype>
    #include<cstring>
    #include<string>
    #include<algorithm>
    using namespace std;
    const int N=30;
    int num[N],n,tot,hash[55],id[3005];
    long long dp[N][50][2525][2],x,y,ans1,ans2;
    inline void pre()
    {
      for(int i=1;i<=2520;i++)
        if(!(2520%i))  hash[++tot]=i,id[i]=tot;
    }
    inline void trans(long long x)
    {
      memset(dp,-1,sizeof(dp));
      long long temp=x;n=0;
      while(temp)  n++,temp/=10;
      temp=x;
      for(int i=n;i>=1;i--)  num[i]=temp%10,temp/=10;
    }
    inline int gcd(int a,int b)
    {
      if(b==0)  return a;
      else return gcd(b,a%b);
    }
    inline int getlcm(int a,int b)
    {
      return a*b/gcd(a,b);
    }
    inline long long dfs(int pos,int gbs,int mod,int lim)
    {
      if(pos==n+1)  return mod%hash[gbs]==0;
      if(dp[pos][gbs][mod][lim]!=-1)  return dp[pos][gbs][mod][lim]; 
      int maxx=lim?num[pos]:9;int vgbs,vmod;long long ans=0;
      for(int i=0;i<=maxx;i++)
      {
        vmod=(mod*10+i)%2520;vgbs=gbs;
        if(i)  
        {  
          vgbs=getlcm(i,hash[gbs]);vgbs=id[vgbs];
        }
        ans+=dfs(pos+1,vgbs,vmod,lim&&i==maxx);
      }
      return dp[pos][gbs][mod][lim]=ans;
    }
    int main()
    {
     // freopen("a.in","r",stdin);
      pre();
      scanf("%I64d%I64d",&x,&y);
      trans(y);ans1=dfs(1,1,0,1);
      trans(x-1);ans2=dfs(1,1,0,1);
      printf("%I64d",ans1-ans2);
      return 0;
    }
     
  • 相关阅读:
    OS快速开发必备
    YYmodel 郭耀源 底层分析
    UITableView使用总结和性能优化
    文件操作
    DOM解析
    SAX解析
    Gallery
    菜单(menu)
    计时器(Chronometer)、标签(TabHost)
    ListActivity
  • 原文地址:https://www.cnblogs.com/AseanA/p/7562325.html
Copyright © 2020-2023  润新知