HDU(4734),数位DP

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

F(x)

Time Limit: 1000/500 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 4389    Accepted Submission(s): 1614


Problem Description
For a decimal number x with n digits (AnAn-1An-2 ... A2A1), we define its weight as F(x) = An * 2n-1 + An-1 * 2n-2 + ... + A2 * 2 + A1 * 1. Now you are given two numbers A and B, please calculate how many numbers are there between 0 and B, inclusive, whose weight is no more than F(A).
 
Input
The first line has a number T (T <= 10000) , indicating the number of test cases.
For each test case, there are two numbers A and B (0 <= A,B < 109)
 
Output
For every case,you should output "Case #t: " at first, without quotes. The t is the case number starting from 1. Then output the answer.
 
Sample Input
3 0 100 1 10 5 100
Sample Output
Case #1: 1 Case #2: 2 Case #3: 13
 
Source
 
第一次接触数位DP,本来是奔着记忆化搜去的。结果发现是个数位DP,收获挺大的。
简单的讲一下我的理解:
起根本还是一个记忆化搜索,主要难处理的地方是下一位的取值(一般是从高到低的遍历),比如说,456,第一位你取的是3,你下一位就可以去0~9,但是如果你取的是4,下一位你就只能取0~5了,这也是最巧妙的地方。
然后我找了一下网上的模板,记一下思路,写的很好。
 
int dfs(int i, int s, bool e) {
    if(i==-1) return s==target_s;
    if(!e && ~f[i][s]) return f[i][s];
    int res = 0;
    int u = e?num[i]:9;
    for(int d = first?1:0; d <= u; ++d)
       res += dfs(i-1, new_s(s, d), e&&d==u);
   return e?res:f[i][s]=res;
}
 
//    pos    = 当前处理的位置(一般从高位到低位)
//    pre    = 上一个位的数字(更高的那一位)
//    status = 要达到的状态,如果为1则可以认为找到了答案,到时候用来返回,
//            给计数器+1。
//    limit  = 是否受限,也即当前处理这位能否随便取值。如567,当前处理6这位,
//            如果前面取的是4,则当前这位可以取0-9。如果前面取的5,那么当前
//            这位就不能随便取,不然会超出这个数的范围,所以如果前面取5的
//            话此时的limit=1,也就是说当前只可以取0-6。
//
//    用DP数组保存这三个状态是因为往后转移的时候会遇到很多重复的情况。
int    dfs(int pos,int pre,int status,int limit)
{
    //已结搜到尽头,返回"是否找到了答案"这个状态。
    if(pos < 1)
        return    status;

    //DP里保存的是完整的,也即不受限的答案,所以如果满足的话,可以直接返回。
    if(!limit && DP[pos][pre][status] != -1)
        return    DP[pos][pre][status];

    int    end = limit ? DIG[pos] : 9;
    int    ret = 0;
    
    //往下搜的状态表示的很巧妙,status用||是因为如果前面找到了答案那么后面
    //还有没有答案都无所谓了。而limti用&&是因为只有前面受限、当前受限才能
    //推出下一步也受限,比如567,如果是46X的情况,虽然6已经到尽头,但是后面的
    //个位仍然可以随便取,因为百位没受限,所以如果个位要受限,那么前面必须是56。
    //
    //这里用"不要49"一题来做例子。
    for(int i = 0;i <= end;i ++)
        ret += dfs(pos - 1,i,status || (pre == 4 && i == 9),limit && (i == end));

    //DP里保存完整的、取到尽头的数据
    if(!limit)
        DP[pos][pre][status] = ret;

    return    ret;
}
#include<cstdio>
#include<cstring>
#define maxn 16

int dp[maxn][111111];
int d[maxn];
int n;
long long tt;

long long  dfs(int len,int pre,bool fp)
{
    if(pre<0) return 0;
    if(!len) return 1;
    if(!fp&&dp[len][pre]!=-1)return dp[len][pre];//记忆化搜索
    int fpmax=fp?d[len]:9;
    int ret=0;
    for(int i=0; i<=fpmax; i++)
    {
        ret+= dfs(len-1,pre-i*(1<<(len-1)),fp&&i==fpmax);
    }
    if(!fp)dp[len][pre]=ret;//记录结果
    return ret;
}

long long  calc(long long a)
{
    int len=0;
    memset(d,0,sizeof(d));
    while(a)
    {
        d[++len]=a%10;
        a/=10;
    }
    return dfs(len,tt,true);
}

int get(int x)
{
    int tmp=1;
    int ans=0;
    while(x)
    {
        ans+=(x%10)*tmp;
        x/=10;
        tmp<<=1;
    }
    return ans;
}

int main()
{
    //freopen("input.txt","r",stdin);
    long long  a,b;
    int nc;
    scanf("%d",&nc);
    int d=1;
    memset(dp,-1,sizeof(dp));
    while(nc--)
    {
        scanf("%I64d%I64d",&a,&b);
        tt=get(a);
        printf("Case #%d: %I64d
",d++,calc(b));
    }
    return 0;
}
原文地址:https://www.cnblogs.com/TreeDream/p/5794787.html