题意:(hdu 4734)

  我们定义十进制数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)的数

#include <stdio.h>
#include <string.h>

int mx[10];
int dp[10][200000];

/*

  <数位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.
*/

int dfs(int len, int pre, bool flag)
{
    if ( len < 0 ) return pre >=0;
    if ( pre < 0 ) return 0;

    if ( !flag && dp[len][pre] != -1) return  dp[len][pre];

    int end = flag?mx[len]:9;
    int ans = 0;
    int i;
    for( i = 0; i <= end; i++)
    {
        ans += dfs(len-1, pre- i*(1<<len), flag&&i==end);
    }
    
    if( !flag )dp[len][pre] = ans;
    return ans;
}

int f(int x)
{
    int ans = 0;
    int tmp = 1;

    while( x )
    {
       ans += (x%10)*tmp;
       tmp = tmp*2;
       x = x/10;
    }

    return ans;
}

int cal(int a, int b)
{
    int tmp = 0;

    while( b )
    {
       mx[ tmp++ ]= b%10;
       b = b/10;
    }
    return dfs(tmp - 1, f(a),  true );
}

int main()
{
    int t;
    int a,b;
    scanf("%d", &t);
    memset(dp, 0xff, sizeof(dp));
    for(int i = 1; i <= t; i++ )
    {
        scanf("%d%d",&a, &b);
        printf("Case #%d: %d\n", i, cal(a,b));
    }
    return 0;
}

 

posted on 2015-04-29 22:44  听风的日子  阅读(154)  评论(0编辑  收藏  举报