• 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;
    }
  • 相关阅读:
    好文章记录
    求职经历
    C正确初始化方式
    linux 常用命令
    MYSQL查找从小到大排列第90%个位置的数据
    最好的单例模式
    <%= %>和${}使用差异
    后台和jsp乱码处理
    浏览器下载文件
    文件下载
  • 原文地址:https://www.cnblogs.com/TreeDream/p/5794787.html
Copyright © 2020-2023  润新知