[关闭]
@myecho 2019-03-18T15:45:49.000000Z 字数 3093 阅读 819

数位DP汇总

动态规划


题意给出1-N之间所有不含49的个数

  1. /*
  2. 题意:求1~N中含有数字49的个数 1 <= N <= 2^63-1
  3. 方法:数位DP
  4. dp[len][0] 代表长度为len不含49的方案数
  5. dp[len][1] 代表长度为len不含49但是以9开头的数字的方案数
  6. dp[len][2] 代表长度为len含有49的方案数
  7. 状态转移如下
  8. dp[i][0] = dp[i-1][0] * 10 - dp[i-1][1]; //如果不含49且开头不为9,在前面可以填上0-9 但是要减去dp[i-1][1] 因为4会和9构成49
  9. dp[i][1] = dp[i-1][0]; //这个直接在不含49的数上填个9就行了
  10. dp[i][2] = dp[i-1][2] * 10 + dp[i-1][1]; //已经含有49的数可以填0-9,或者9开头的填4
  11. 写完动态转移方程后就把N从高位到低位一个一个统计了
  12. 在统计到某一位的时候,加上dp[i-1][2] * digit[i] 是显然没问题,这是因为这一位可以填【0,(digit[i]-1)】这个区间的数
  13. 若这一位之前挨着49,那么加上 dp[i-1][0] * digit[i] 也是显然OK。
  14. 若这一位之前没有挨着49,但是digit[i]比4大,那么当这一位填比digit[i]小的4的时候,就得加上dp[i-1][1](以9开头的数字的方案数)
  15. */
  16. #include<iostream>
  17. #include<cstdio>
  18. #include<cstdlib>
  19. #include<algorithm>
  20. #include<cmath>
  21. #include<map>
  22. #include<cstring>
  23. #include<string>
  24. #include<set>
  25. #include<stack>
  26. #include<queue>
  27. #include<iomanip>
  28. const int MAX=21;
  29. __int64 dp[MAX][3];
  30. int digit[MAX];
  31. using namespace std;
  32. int main()
  33. {
  34. memset(dp,0,sizeof(dp));
  35. dp[0][0]=1; //边界
  36. for(int i=1;i<20;i++)
  37. {
  38. dp[i][0]=dp[i-1][0]*10-dp[i-1][1];
  39. dp[i][1]=dp[i-1][0];
  40. dp[i][2]=dp[i-1][2]*10+dp[i-1][1];
  41. }
  42. int t,len,i,j,p,before;
  43. __int64 sum,n;
  44. cin>>t;
  45. while(t--)
  46. {
  47. cin>>n;
  48. memset(digit,0,sizeof(digit));
  49. len=0;
  50. sum=0;
  51. before=0;
  52. while(n)
  53. {
  54. digit[++len]=n%10; //高位在右边
  55. n/=10;
  56. }
  57. bool p=false;
  58. for(i=len;i>=1;i--)
  59. {
  60. sum += dp[i-1][2] * digit[i];
  61. if(p)
  62. sum += dp[i-1][0] * digit[i];
  63. if(!p && digit[i] >4) //必须要有!p因为dp[i-1][0]的情况包括了dp[i-1][1]
  64. sum += dp[i-1][1];
  65. if(before == 4 && digit[i] == 9)
  66. p = true;
  67. before = digit[i];
  68. }
  69. if(p)
  70. sum++; //本身也算一个
  71. cout<<sum<<endl;
  72. }
  73. return 0;
  74. }
  75. /*统计过程如下:
  76. 假设统计N=591时,那么按以下的顺序进行统计:
  77. 1~499 确定5这一位,统计的数比它小
  78. 500~589 确定9这一位 ,统计的数比它小
  79. 590 确定1这一位,统计的数比它小
  80. 最后判断一下自身是不是符合 即591
  81. */

常见转化的技巧,如果给定的区间是[n,m]的话,需要转化为[0,m] - [0,n]的问题形式进行求解。

例题2:
求不含62和4的数目在区间[n,m]内,转化为求[n,m]区间内含有62或者4的问题~

  1. //dp[i][0]:长度为i的不含62和4的个数
  2. //dp[i][1]:长度为i,且最高位含有2的不含62和4的个数
  3. //dp[i][2]:长度为i的含有62或4的个数
  4. void init()
  5. {
  6. memset(dp,0,sizeof(dp));
  7. dp[0][0] = 1;
  8. for(int i=1;i<=8;i++)
  9. {
  10. dp[i][0] = dp[i-1][0]*9(前边不能加4,其他随便) - dp[i-1][1](62会组成62);
  11. dp[i][1] = dp[i-1][0];(前边添个2)
  12. dp[i][2] = dp[i-1][0](前边加个4) + dp[i-1][1](前边加个6) + dp[i-1][2] * 10(随便加);
  13. }
  14. }
  15. 统计过程:
  16. for(int i=m;i>=1;i--)
  17. {
  18. ans += dp[i-1][2] * A[i];
  19. if(flag)
  20. {
  21. ans += dp[i-1][0] * A[i];
  22. }
  23. else
  24. {
  25. if(A[i]>4) ans += dp[i-1][0]; //填个4
  26. if(A[i+1] == 6 && A[i]>2) ans += dp[i][1];//很重要,上题没有统计是因为9是最大的数字了!
  27. if(A[i]>6) ans += dp[i-1][1];
  28. if(A[i] == 4 || (A[i+1] == 6 && A[i] == 2)) flag = true;
  29. }
  30. }
  31. //数本身
  32. if(flag) ans++;

时刻注意不要重复和遗漏!
数位DP论文: 刘聪 http://wenku.baidu.com/link?url=ST9zKTsTISzDSKh-qWCWj4avfyrwY47AVAZyMZnDyUNRjLAnRKwRiyKOR0rBPBqUryCFbkf2qe6g7u36bIqiCXz-uBm4oq1B_bH-rXJY_Di###

拓展的题目: 这类题目需要经过转化之后才能看出数位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]

第二题:HDU 3652 给定区间[1,N]求出含13且是13的倍数的数的数目。

关于整除的问题也是可以利用数位dp的,原因在于a%m == ((b%m)*10 + d)%m ,假如 a == bd的形式的话

实际上用3维就可以,参考资料2中给出的解答是用的4维的数组。
可以设DP[I][J][K]令J为除13后的余数,I为位数,K代表位数中是否出现13
其中k可以为0,1,2分别代表没有出现13,出现13,以及没出现13但是首位为1的情况。
题解:http://www.cnblogs.com/jie-dcai/p/4278404.html

参考资料:
1. 汇总:http://blog.csdn.net/niuox/article/details/9864199
2. http://wenku.baidu.com/link?url=o3ER_gVCyB0qcKthM-Y8vPtAGZ_u5bzOu_gUCUhPcXC6YfaSDgtBSXNEEvvGvSzyuDE9TULcPNsDrRd9IUtQVHeKUVrnPUjyfWjCly_J7Xq 初探数位DP

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注