zoukankan      html  css  js  c++  java
  • [HDU4734] F(x)(数位dp+优化)

    >传送门<
    题意:对于一个有n位(这n位从高位到低位分别是AnAn-1An-2 ... A2A1)的十进制数,我们定义它的权值F(x)=An*2n-1 + An-1*2n-2 + ... + A2*2 + A1*1.现在给你两个数AB,请计算[0,B]范围内有多少个权值<=F(A)的数

    思路:(这个下面文字是有点多,可能是我太弱了,在刚开始学的时候只有这样我才能理解QAQ~)

    其实F(x)只是给每一个数位带上一个权值v = 2^(p-1)F(x)最大是值不会超过5000,我们完全可以抛开权值来思考,写代码的时候再加上权值即可,这样思考和写草稿之类的会方便很多,不考虑每一位的权值的话即是数位的前缀和。

    很容易想到一个dp式是dp[pos][sum];表示当前在第pos位,前缀和为sum的答案,快速把数位dp敲完,交上去,然后会发现TLE掉了,这题的时限只有500ms,TLE的原因是什么呢,就是记忆化不够彻底

    在做数位dp入门题 不要62 的时候可能有些人会注意到,dp数组只需要初始化一次即可,这是因为不要62题意中是不包含4和连续的62的数的个数,这里的条件是一个数本身的性质,也就是说,一个数有没有4或者连续的62和你输入的[l,r]区间是无关的,比如1234是不合法的,无论你输入1 - 1000 还是 1 - 10000,1234都是不合法的。

    但是这里是不一样的,题意要求小于F(a),而a是输入的,而dp数组定义是:dp[pos][sum],表示当前在第pos位,前缀和为sum的答案, 如假设上限是55555, 那么123xxx 和 321xxx 的答案都是 dp[3][6] ,这就是记忆化的结果,因为我不需要考虑前面具体的数的123 还是321 114之类的,只要他们的前缀和sum相同并且在同样的数位,后面xxx的情况都是一样的,因为后面的约束条件都是和不大于F(a)-sum。

    问题就是在这个地方,由于我的记忆化是和输入的a也就是F(a)有关,所以我不能把它当作一个数的性质来记忆话,比如同样是123xxx,对于F(a) = 10F(a) = 20,后面xxx可行的情况是不一样的,a=10的时候,后面的约束是不大于F(a)-sum = 4a = 20的时候约束为不大于F(a)-sum = 14,从而导致了每次输入不同的a都要重新初始化dp数组,重新搜索一次。这里可以通过对dp状态的定义做一点小小的改变,使得约束条件变为与F(a)无关,从而不需要每次初始化重新搜索,定义dp[pos][sum],表示当前在第pos位,F(a)-sum的答案sum为到第pos位为止的数位前缀和),这时在第pos位的约束条件是后面所有的数的和不大于sum这时记忆化的是sum,是与F(a)无关的,比如当F(a) = 10F(a) = 20时,F(a) = 10时,123xxx、321xxx等对应的是dp[3][4] F(a)=20时,556xxx、466xxx对应的也是dp[3][4],因为2种情况的posF(a)-sum都是相同的,所以记忆化一次即可,不必初始化

    总结一下,在设计数位dp状态时最好把每次询问的变量作为初始值而不是状态转移、判定时依赖的量(比如第一个状态判定条件是F(x)<=F(a),就依赖于当前F(a),这样就不好),这样才可以最大限度的发挥记忆化的作用。

     Code

    #include<cstdio>
    #include<cstring>
    using namespace std;
    const int maxn = 1e4+5;
    
    int A, B, t, tot;
    int a[12], dp[12][maxn];
    int f(int x) {
        int ans = 0, cnt = 0;
        while(x){
            ans += (x%10)*(1<<(cnt++));
            x /= 10;
        }
        return ans;
    }
    int dfs(int pos, int sum, int limit) {
        if(pos==0) return sum<=tot;
        if(sum>tot) return 0;
        if(!limit&&dp[pos][tot-sum]!=-1) return dp[pos][tot-sum];
        int up = limit?a[pos]:9;
        int ans = 0;
        for(int i = 0; i <= up; i++)
            ans += dfs(pos-1, sum+i*(1<<(pos-1)), limit&&i==a[pos]);
        if(!limit) dp[pos][tot-sum] = ans;
        return ans;
    }
    int solve(int x) {
        int pos = 1;
        while(x) {
            a[pos++] = x%10;
            x /= 10;
        }
        return dfs(pos-1, 0, 1);
    }
    int main()
    {
        int kase = 1;
        memset(dp,-1,sizeof dp);
        scanf("%d", &t);
        while(t--) {
            scanf("%d%d", &A, &B);
            tot = f(A);
            printf("Case #%d: %d
    ", kase++, solve(B));
        }
        return 0;
    }
    View Code

     参考文章:

    https://www.cnblogs.com/AbandonZHANG/p/4114122.html

    http://www.bubuko.com/infodetail-2320548.html

    https://blog.csdn.net/wust_zzwh/article/details/52100392

    https://www.cnblogs.com/herumw/p/9464514.html

  • 相关阅读:
    4月24日 PHP基础
    4月22日 常用函数
    4月22日 练习题
    PHP正则数组
    PHP基础函数应用
    数据库SQL语句
    高级查询
    mysql
    CSS样式表
    词汇
  • 原文地址:https://www.cnblogs.com/wizarderror/p/11347119.html
Copyright © 2011-2022 走看看