题意:
我们定义十进制数x的权值为f(x) = a(n)*2^(n-1)+a(n-1)*2(n-2)+...a(2)*2+a(1)*1,a(i)表示十进制数x中第i位的数字。
题目给出a,b,求出0~b有多少个不大于f(a)的数。
思路:
数位DP,用来学习数位DP了。
<数位DP>
所谓数位DP就是基于考虑数字的每一位来转移的DP。
例如求比456小的数,可以这么考虑,
4 5 6
4 5 (0~6)
4 (0~4) (0~9)
(0~3)(0~9) (0~9)
然后我们就可以考虑用dp[len][pre]表示长度为len,以pre开头的符合条件的数的个数。
这样就可以得到转移方程了。
而对于这道题,我们可以用dp[len][pre]表示长度为len且权值不大于pre的数。
这道题用记忆化搜索,除边界条件外记录dp[len][pre]的值,下一次发现以前已经计算过了就可以直接return;
初值:dp[len][pre] = 0;
dfs(len, pre, flag)表示求长度为len,不超过pre的所有符合条件的值。其中flag是用来控制边界的。
dfs过程中当深搜的边界,发现len < 0,pre >=0 的时候就返回1.
Tips:
下面记忆的时候要记得判断是不是边界,如果是边界算出来的答案是不完整的。
Code:
1 /****************************************** 2 *Author: Griselda 3 *Created Time: 2013-11-17 18:38 4 *Filename: 4734.cpp 5 * ****************************************/ 6 #include <stdio.h> 7 #include <cstring> 8 #include <algorithm> 9 using namespace std; 10 11 int dp[10][200000], mx[10]; 12 int dfs(int len, int pre, bool flag) 13 { 14 if (len < 0) return pre >= 0; 15 if (pre < 0) return 0; 16 if (!flag && dp[len][pre] != -1) return dp[len][pre]; 17 int end = flag?mx[len]:9, ans = 0; 18 for (int i = 0; i <= end; ++i) { 19 ans += dfs(len-1, pre-i*(1<<len), flag&&i==end); 20 } 21 if (!flag) dp[len][pre] = ans; 22 return ans; 23 } 24 25 int f(int x) 26 { 27 int tmp = 1, ans = 0; 28 while (x) { 29 ans += x%10*tmp; 30 x /= 10; 31 tmp *= 2; 32 } 33 return ans; 34 } 35 36 int cal(int a, int b) 37 { 38 int top = 0; 39 while (b) { 40 mx[top++] = b%10; 41 b /= 10; 42 } 43 return dfs(top-1, f(a), true); 44 } 45 46 int main() 47 { 48 int iCase = 1, nCase; 49 int a, b; 50 scanf("%d", &nCase); 51 memset(dp, 0xff, sizeof(dp)); 52 while (nCase--) { 53 scanf("%d %d", &a, &b); 54 printf("Case #%d: %d\n", iCase++, cal(a, b)); 55 } 56 return 0; 57 }