• 数位DP问题整理(一)


    第一题:Amount of degrees (ural 1057)

    题目链接:http://acm.timus.ru/problem.aspx?space=1&num=1057

    题意:[x,y]范围内的数,可以拆分成k个b进制的不同幂的和 的数字有多少。

    我们可以将x转换成二进制来讨论。二进制转化时,找到第一个非0非1的数,将其及其后面的数都变为1.

    那么问题就变成了求[0,x]范围内,二进制表示中含有k个1的数字有多少个。

    求[x,y]区间相减。我们可以给数建立0,1的表示树。

    在求高度为i的完全二叉树中含有j个1的路径有多少个时,递推式为:f[i,j] = f[i-1,j-1] + f[i-1,j]

    #include <iostream>
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <math.h>
    #include <map>
    #include <queue>
    #include <algorithm>
    using namespace std;
    
    int f[35][35];
    int d[35];
    //高度为i(i>=0)时,含有j个1的个数
    void init()
    {
       memset(f,0,sizeof(f));
       f[0][0] = 1;
       for(int i=1;i<=31;i++)
       {
          f[i][0] = 1;
          for(int j=1;j<=i;j++)
          {
             f[i][j] = f[i-1][j-1] + f[i-1][j];
          }
       }
    }
    //[0,x]范围内二进制含有k个1的个数
    int calc(int x,int k)
    {
       //路径上含有的1的个数
       int tot = 0;
       int ans = 0;
       for(int i=31;i>0;i--)
       {
          if(x&(1<<i)) 
          {
             tot++;
             if(tot>k) break;
             x ^= (1<<i);
          }
          if((1<<(i-1))<=x) ans += f[i-1][k-tot];
       }
       if(tot + x == k) ans++;
       return ans;
    }
    //b进制转化为二进制
    int transfer(int b,int x)
    {
       int m = 0;
       int ans = 0;
       while(x)
       {
          d[m++] = x % b;
          x/=b;
       }
       for(int i=m-1;i>=0;i--)
       {
          if(d[i]>1) 
          {
             for(int j=i;j>=0;j--) ans |= (1<<j);
          }
          else ans |= d[i]<<i;
       }
       return ans;
    }
    int main()
    {
       #ifndef ONLINE_JUDGE
          freopen("in.txt","r",stdin);
       #endif
       int x,y;
       int k,b;
       init();
       while(scanf(" %d %d",&x,&y)!=EOF)
       {
          scanf(" %d %d",&k,&b);
          x = transfer(b,x-1);
          y = transfer(b,y);
          printf("%d
    ",calc(y,k) - calc(x,k));
       }
       return 0;
    }
    

    第二题:windy数。

    题意:求给定区间范围内的,求相邻数位之差绝对值不小于2的数的个数。

    题目链接:http://www.lydsy.com/JudgeOnline/problem.php?id=1026

    #include <iostream>
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <math.h>
    #include <map>
    #include <queue>
    #include <algorithm>
    using namespace std;
    
    int A[12];
    
    int f[12][10];
    
    //f[i][j]代表长度为i,最高位为j的windy数个数
    void init()
    {
       memset(f,0,sizeof(f));
       for(int i=0;i<10;i++) f[1][i] = 1;
       for(int i=2;i<=10;i++)
       {
          for(int j=0;j<10;j++)
          {
             for(int k=0;k<10;k++)
             {
                if(abs(j-k)>1) f[i][j] += f[i-1][k];
             }
          }
       }
    }
    //(0,a)范围内的windy数个数
    int calc(int a)
    {
       int m = 0;
       while(a)
       {
          A[m++] = a%10;
          a/=10;
       }
       int ans = 0;
       //先处理长度小于m的windy数的个数
       for(int i=1;i<m;i++)
       {
          //题目要求不含前导0
          for(int j=1;j<10;j++)
          {
             ans += f[i][j];
          }
       }
       //长度等于m且最高位和原数不同且小于原数的windy数
       for(int j=1;j<A[m-1];j++) ans += f[m][j];
       //依次循环将最高位 变为和原数相同
       for(int i=m-1;i>=1;i--)
       {
          for(int j=0;j<A[i-1];j++)
          {
             if(abs(j-A[i]) > 1) ans += f[i][j];
          }
          if(abs(A[i] - A[i-1])<=1) break;
       }
       return ans;
    }
    
    
    int main()
    {
       #ifndef ONLINE_JUDGE
          freopen("in.txt","r",stdin);
       #endif
       int a,b;
       init();
       while(scanf(" %d %d",&a,&b)!=EOF)
       {
          int ans = calc(b+1) - calc(a);
          printf("%d
    ",ans );
       }
       return 0;
    }
    


    第三题:Hdu 2089 不要62

    题目连接:http://acm.hdu.edu.cn/showproblem.php?pid=2089

    求给定区间中不含有62和4的数的个数。

    #include <iostream>
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <math.h>
    #include <map>
    #include <queue>
    #include <algorithm>
    using namespace std;
    
    int dp[10][3];
    
    int A[10];
    
    //(0,a]范围内有多少个吉利数
    int calc(int a)
    {
       int sum = a;
       int m = 0;
       int ans = 0;
       bool flag = false;
       while(a)
       {
          A[++m] = a%10;
          a/=10;
       }
       A[m+1] = 0;
       for(int i=m;i>=1;i--)
       {
          ans += dp[i-1][2] * A[i];
          if(flag)
          {
             ans += dp[i-1][0] * A[i];
          }
          else
          {
             if(A[i]>4) ans += dp[i-1][0];
             if(A[i+1] == 6 && A[i]>2) ans += dp[i][1];
             if(A[i]>6) ans += dp[i-1][1];
             if(A[i] == 4 || (A[i+1] == 6 && A[i] == 2)) flag = true;
          }
       }
       //数本身
       if(flag) ans++;
       return sum - ans;
    }
    
    //dp[i][0]:长度<=i的吉利数个数
    //dp[i][1]:长度为i,且最高位含有2的吉利数个数
    //dp[i][2]:长度<=i的非吉利数个数
    void init()
    {
       memset(dp,0,sizeof(dp));
       dp[0][0] = 1;
       for(int i=1;i<=8;i++)
       {
          dp[i][0] = dp[i-1][0]*9 - dp[i-1][1];
          dp[i][1] = dp[i-1][0];
          dp[i][2] = dp[i-1][0] + dp[i-1][1] + dp[i-1][2] * 10; 
       }
    }
    
    int main()
    {
       #ifndef ONLINE_JUDGE
          freopen("in.txt","r",stdin);
       #endif
       int a,b;
       init();
       while(scanf(" %d %d",&a,&b)!=EOF)
       {
          if(a == 0 && b == 0) break;
          int ans = calc(b) - calc(a-1);
          printf("%d
    ",ans);
       }
       return 0;
    }
    


    第四题:Hdu 3555 Bomb

    题目连接:http://acm.hdu.edu.cn/showproblem.php?pid=3555

    题意:求给定区间的含有49的数的个数。

    方法与上题类似,比上题要简单许多。

    #include <iostream>
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <math.h>
    #include <map>
    #include <queue>
    #include <algorithm>
    using namespace std;
    
    #define LL __int64
    LL dp[25][3];
    
    int A[25];
    
    //(0,a]范围内有多少个吉利数
    LL calc(LL a)
    {
       int m = 0;
       LL ans = 0;
       bool flag = false;
       while(a)
       {
          A[++m] = a%10;
          a/=10;
       }
       A[m+1] = 0;
       for(int i=m;i>=1;i--)
       {
          ans += dp[i-1][2] * A[i];
          if(flag)
          {
             ans += dp[i-1][0] * A[i];
          }
          else
          {
             if(A[i]>4) ans += dp[i-1][1];
             if(A[i+1] == 4 && A[i] == 9) flag = true;
          }
       }
       //数本身
       if(flag) ans++;
       return ans;
    }
    
    //dp[i][0]:长度<=i的不含49的数的个数
    //dp[i][1]:长度为i,且最高位含有9的不含49的数的个数
    //dp[i][2]:长度<=i的含有49的数个数
    void init()
    {
       memset(dp,0,sizeof(dp));
       dp[0][0] = 1;
       for(int i=1;i<=22;i++)
       {
          dp[i][0] = dp[i-1][0]*10 - dp[i-1][1];
          dp[i][1] = dp[i-1][0];
          dp[i][2] = dp[i-1][2] * 10 + dp[i-1][1];
       }
    }
    
    int main()
    {
       #ifndef ONLINE_JUDGE
          freopen("in.txt","r",stdin);
       #endif
       int t;
       LL a;
       init();
       scanf(" %d",&t);
       while(t--)
       {
          scanf(" %I64d",&a);
          LL ans = calc(a);
          printf("%I64d
    ", ans);
       }
       return 0;
    }
    

    第五题:Hdu 3709 Balanced Number

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=3709

    平衡数。枚举平衡位置。采用记忆化搜索的方式记录已有的值。加适当剪枝。然后排除掉重复的0即可。

    #include <iostream>
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <math.h>
    #include <map>
    #include <queue>
    #include <algorithm>
    using namespace std;
    
    #define LL long long
    #define Maxn 20
    
    LL dp[Maxn][Maxn][2005];
    int digit[Maxn];
    
    LL dfs(int pos,int pivot,int pre,bool limit)
    {
       if(pos<=0) return pre == 0;
       if(pre<0) return 0;
       if(!limit && dp[pos][pivot][pre]!=-1) return dp[pos][pivot][pre];
       int end = limit ? digit[pos] : 9;
       LL ans = 0;
       for(int i=0;i<=end;i++)
       {
          ans += dfs(pos-1,pivot,pre + i*(pos-pivot),limit && (i == end));
       }
       if(!limit) dp[pos][pivot][pre] = ans;
       return ans;
    }
    
    LL calc(LL a)
    {
       if(a<0) return 0;
       int len = 0;
       LL ans = 0;
       while(a>0)
       {
          digit[++len] = a%10;
          a/=10;
       }
       for(int i=1;i<=len;i++)
       {
          ans += dfs(len,i,0,1);
       }
       ans = ans - len + 1;
       return ans;
    }
    int main()
    {
       #ifndef ONLINE_JUDGE
          freopen("in.txt","r",stdin);
       #endif
       int t;
       LL x,y;
       scanf(" %d",&t);
       memset(dp,-1,sizeof(dp));
       while(t--)
       {
          
          scanf(" %I64d %I64d",&x,&y);
          printf("%I64d
    ",calc(y) - calc(x-1) );
       }
       return 0;
    }
    

    第六题:Hoj 1983 Beautiful numbers

    题目链接:http://acm.hit.edu.cn/hoj/problem/view?id=1983

    题意:如果一个数能够被其每个数位的数都整除,那么这个数就叫做美丽数。

    基本思路是用:dp[len][mod][lcm]表示<=len的长度中,此数为mod,各数位的最小公倍数为lcm的数的个数来进行记忆化搜索。方法和上一题类似。

    但我们发现,len在[1,20]范围内,mod在[1,1^18]范围内,lcm在[1,2520]范围内。所以dp数组肯定超内存。

    下面我们来进行内存优化:

    假设这个数为a,各个数位的值分别为ai,那么我们发现lcm(ai) | a.

    而[1,9]的最小公倍数是2520.那么lcm(ai) | 2520, 所以lcm(ai) | (a%2520).

    所以第二维大小我们可以从1^18降到2520,方法是%2520.

    现在的dp数组的内存是20*2520*2520,还是很大。

    然后我们再考虑:

    我们发现某一个数的各个数位的数的最小公倍数最大是2520,而且只能是2520的公约数。而2520的公约数有48个。所以第三维我们只用[50]的空间就行了。

    方法是用Hash进行离散化。‘

    这样内存就成了20*2520*50,可以拿下这道题目了。

    #include <iostream>
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <math.h>
    #include <map>
    #include <queue>
    #include <algorithm>
    using namespace std;
    
    #define LL long long
    
    LL dp[20][2525][55];
    int digit[20];
    int hash[2525];
    
    int gcd(int a,int b)
    {
       if(b == 0) return a;
       return gcd(b,a%b);
    }
    int calc_lcm(int a,int b)
    {
       return a/gcd(a,b)*b;
    }
    LL dfs(int pos,int mod,int lcm,bool limit)
    {
       LL ans = 0;
       if(pos<=0) return mod % lcm == 0;
       if(!limit && dp[pos][mod][hash[lcm]]!=-1) return dp[pos][mod][hash[lcm]];
       int end = limit ? digit[pos] : 9;
       for(int i=0;i<=end;i++)
       {
          ans += dfs(pos-1,(mod*10+i)%2520,i?calc_lcm(lcm,i):lcm,limit && (i==end));
       }
       if(!limit) dp[pos][mod][hash[lcm]] = ans;
       return ans;
    }
    
    LL calc(LL a)
    {
       if(a<0) return 0;
       int len = 0;
       while(a>0)
       {
          digit[++len] = a%10;
          a/=10;
       }
       //0也当作其中的一个美丽数,因为两者相减会抵消掉
       LL ans = dfs(len,0,1,1);
       return ans;
    }
    void init()
    {
       memset(dp,-1,sizeof(dp));
       int id = 0;
       for(int i=1;i*i<=2520;i++)
       {
          if(2520%i == 0)
          {
             hash[i] = id++;
             if(i*i!=2520) hash[2520/i] = id++;
          }
       }
       //printf("id = %d
    ", id);
    }
    int main()
    {
       #ifndef ONLINE_JUDGE
          freopen("in.txt","r",stdin);
       #endif
       init();
       int t;
       LL x,y;
       while(scanf(" %lld %lld",&x,&y)!=EOF)
       {
          printf("%lld
    ",calc(y) - calc(x-1));
       }
       return 0;
    }
    

    第七题:吉哥系列故事——恨7不成妻

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=4507

    与上一题做法也类似,只不过dp需要保存三种值,所以把它结构体了

    #include <iostream>
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <math.h>
    #include <map>
    #include <queue>
    #include <algorithm>
    using namespace std;
    
    #define MOD 1000000007
    #define LL __int64
    
    int digit[20];
    LL power[20];
    
    struct  Node
    {
       LL n,s,sq;
    }dp[20][10][10];
    
    Node dfs(int pos,int mod,int modSum,bool limit)
    {
       if(pos<=0)
       {
          Node t;
          t.n = (mod!=0 && modSum!=0);
          t.s  = t.sq = 0;
          return t;
       }
       if(!limit && dp[pos][mod][modSum].n!=-1) return dp[pos][mod][modSum];
       int end = limit ? digit[pos] : 9;
       Node ans,temp;
       ans.n = ans.s = ans.sq = 0;
       for(int i=0;i<=end;i++)
       {
          if(i == 7) continue;
          temp = dfs(pos-1,(mod*10+i)%7,(modSum+i)%7,limit && (i == end));
          ans.n = (ans.n + temp.n)%MOD;
          ans.s = (ans.s + temp.s + ((i * power[pos])%MOD * temp.n) % MOD) % MOD ;
          ans.sq = (ans.sq + temp.sq + ((2*i*power[pos])%MOD*temp.s)%MOD + (((i*i*power[pos])%MOD*power[pos])%MOD*temp.n)%MOD)%MOD;
       }
       if(!limit) dp[pos][mod][modSum] = ans;
       return ans;
    }
    LL calc(LL a)
    {
       int len = 0;
       while(a>0)
       {
          digit[++len] = a%10;
          a/=10;
       }
       Node ans = dfs(len,0,0,true);
       return ans.sq;
    }
    void init()
    {
       memset(dp,-1,sizeof(dp));
       memset(power,0,sizeof(power));
       power[1] = 1;
       for(int i=2;i<=19;i++)
       {
          power[i] = (power[i-1] * 10)%MOD;
       }
    }
    int main()
    {
       #ifndef ONLINE_JUDGE
          freopen("in.txt","r",stdin);
       #endif
       int t;
       LL l,r;
       init();
       scanf(" %d",&t);
       while(t--)
       {
          scanf(" %I64d %I64d",&l,&r);
          LL ans = (calc(r) - calc(l-1) + MOD)%MOD;
          printf("%lld
    ", ans);
       }
       return 0;
    }
    



  • 相关阅读:
    Realtime crowdsourcing
    maven 常用插件汇总
    fctix
    sencha extjs4 command tools sdk
    首次吃了一颗带奶糖味的消炎药,不知道管用不
    spring mvc3 example
    ubuntu ati driver DO NOT INSTALL recommand driver
    yet another js editor on windows support extjs
    how to use springsource tools suite maven3 on command
    ocr service
  • 原文地址:https://www.cnblogs.com/pangblog/p/3257857.html
Copyright © 2020-2023  润新知