HDU 3652 数位DP变形

来源:互联网 发布:张馨予的淘宝店铺名字 编辑:程序博客网 时间:2024/06/06 12:33

B-number

Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 5599    Accepted Submission(s): 3227


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
131002001000
 

Sample Output
1122
 



关于这道题,网上有很多优秀的代码和详细的注释,特别推荐这篇博文:

http://blog.csdn.net/libin56842/article/details/10026063

注释写得很详细,但我才看时却知其然而不知其所以然,后来通过了一上午的调试和纸上模拟,弄清了其过程,并对记忆化搜索有了一些自己的理解:

其实跟预处理递推的思路类似,你可以把数位DP看成了一个特殊的枚举查找,特殊之处在于其记忆化
举例说明,比如此题,我们取n = 999:

我们从三位数开始搜索,我们先算不大于m = 100的合法数(即满足含13又能整除13)个数。

而算不大于m的合法数,我们又要分别枚举:

0~10,11~20,21~30,31~40,41~50.....91~100各个区间的合法数个数。

而对于0~10区间,我们只能一个个枚举:

0非法...
1非法...
2非法...
......
10非法....

然后全部依次枚举之后,我们就得到了不大于100的合法数只有一个 13。

上述过程跟普通的枚举没什么区别,但中间少了一个重要的步骤,它就是记忆化。
所谓记忆化,就是当我们把0~10的区间全部枚举完之后,我们用一个数组保存我们之前枚举的结果。

然后当我们枚举101~200区间的合法数时,当我们已经知道0~10区间没有合法数,而一个非法一位数x,变成两位数2x 一定仍然是非法数。
所以我们就不会去重复枚举200~210内的数是否合法。

没错,记忆化的作用就是减少重复的枚举,加快我们得到目标解的速度。

那如何记忆化呢?
我们这道题用dp[i][j][k]来保存之前枚举的结果:
i:数的位数
j:此时除13的余数。
k:状态值status:    0: 末尾不是1的合法数
1:末尾是1的合法数。
2:非法数

而什么时候该保存结果,什么时候不该保存呢?
所以我们需要借助一个参数limit,但此时是一个无限制性的枚举如我们枚举0~9,位数为1的已经全部枚举,这时我们便可以保存此时的值。
而如果是有限制性的枚举,如我们只求小于300的合法数,此时求出的合法数结果并不能代表所有位数为3,余数状态相同的所有数的枚举结果,因为其他大于300的三位合法数我们并没有枚举,所以我们此时不应该保存这个特殊情况下的结果。

下面是代码:



#include <bits/stdc++.h>using namespace std;int dp[15][15][3],digit[15];int dfs(int len,int mod,int status,bool limit){if(len <= 0) return mod == 0 && status == 2;if(!limit && dp[len][mod][status] >= 0)return dp[len][mod][status];int i,ans = 0,num = (limit?digit[len]:9);int mod_x,status_x;for(i=0 ;i<=num ;i++){mod_x = (mod*10 + i) % 13;status_x = status;if(status == 0 && i == 1){status_x = 1;}if(status == 1 && i != 1){status_x = 0;}if(status == 1 && i == 3){status_x = 2;}ans += dfs(len-1,mod_x,status_x,limit&&i==num);}return (limit?ans:dp[len][mod][status] = ans);}int solve(int x){int len = 0,i;while(x){digit[++len] = x%10;x /= 10;}digit[len+1] = 0;return dfs(len,0,0,true);}int main(){int n;memset(dp,-1,sizeof(dp));while(scanf("%d",&n) != EOF){printf("%d\n",solve(n));}return 0;}


0 0