HDU-4734-F(x)

HDU4734F(x)

题目传送门

一、题目描述

题目给了个F(x)的定义:F(x)=An2n1+An12n2+...+A221+A120Ai是十进制数位,然后给出ab,求区间[0,b]内满足f(i)<=f(a)i的个数。其中 0<=A,B<109

二、计算题目中的F函数

#include <bits/stdc++.h>

using namespace std;
/**
12345-->f(12345)=1*2^4+2*2^3+3*2^2+4*2^1+5*2^0
         f(1234) =1*2^3+2*2^2+3*2^1+4*2^0
       2*f(1234) =1*2^4+2*2^3+3*2^2+4*2^1
所以形成递归表示式
*/
//利用递归计算出F(x)
int f(int x) {
    if (x == 0) return 0;
    return f(x / 10) * 2 + x % 10;
}

//利用循环进行计算
int f2(int x) {
    int res = 0;
    int i = 0;
    while (x) {
        res += x % 10 * (1 << i);
        i++;
        x /= 10;
    }
    return res;
}

int main() {
    cout << f(12345) << endl;
    cout << f2(12345) << endl;
    //极大值是1e9-1=99999999
    cout << f2(1e9 - 1) << endl;
    //输出4599
    return 0;
}

三、暴力怎么做?

用例解析:
5 100 为例:即A=5,B=100
F(A)=51=5
也就是让我们求一下,在F(0)~F(100)之间,有多少个F(x)<=5

撸起袖子就是干!

#include <bits/stdc++.h>

using namespace std;
int F(int x) {
    if (x == 0) return 0;
    return F(x / 10) * 2 + x % 10;
}
int fa;
int main() {
    int T;
    int num = 1;
    cin >> T;

    while (T--) {
        int x, y;
        cin >> x >> y;
        fa = F(x);
        int cnt = 0;
        for (int i = 0; i <= y; i++)
            if (F(i) <= fa) cnt++;

        printf("Case #%d: %d\n", num++, cnt);
    }
    return 0;
}

毫无疑问,如此暴力,只能是TLE,看了一下时间限制,要求500ms,应该是一个数位DP问题。

四、正常的数位DP思路

1、思考暴力dfs怎么做:

int dfs(int pos,int sum,bool limit)

  • pos:我在哪里

  • sum:我这个分身,走到现在,按F(x)的计算办法,已经拼接出的加权前缀和是多少,之所以要携带这个,是因为走完全程时需要判断总的sum是不是小于F(A)

  • limit:是不是贴上界,之所以需要知道这个,是因为每位都需要上一位告诉自己,自己及以后各位是不是能取满,还是只能取一部分

返回值int类型的dfs,思路就是 向后思考 :如果我站在pos位置,并且前面的人告诉了我:以前有多少sum前缀和,是不是贴上界,
然后,我的任务就是计算,在给定的条件下,后续的枚举所有可能数字中,有多少符合条件的数字。

根据这个返回值定义,所以整体的结果就是:dfs(al,0,true)

2、记忆化

根据我们的经验,500ms直接暴力dfs应该也不可能过的了,必须考虑是不是存在重复计算,能不能记忆化。简单想一想,肯定是可以记忆化的:到达哪种状态后,我不管你前面怎么到达我这里,一旦我的状态确定,那么我的后续所有可能中,符合条件的数字个数是一定的,我计算一次,想办法记下来,下回就可以重复利用。

那么按什么来记忆呢?根据经验,limit一般不记,只记忆!limit的状态结果,所以dp[pos][sum]的状态记忆数组定义就出来了。

代码也就很简单了:

#include <cstdio>
#include <cstring>
#include <iostream>
#include <cmath>

using namespace std;
const int N = 32;
const int M = 1024 * 10 + 10;
int f[N][M];
int a[N];
int fa;
int F(int x) {
    if (x == 0) return 0;
    return F(x / 10) * 2 + x % 10;
}

int dfs(int pos, int sum, bool limit) {
    if (pos == 0) return sum <= fa;
    if (sum > fa) return 0;
    if (!limit && ~f[pos][sum]) return f[pos][sum];
    int ans = 0;
    int up = limit ? a[pos] : 9;
    for (int i = 0; i <= up; i++)
        ans += dfs(pos - 1, sum + i * (1 << (pos - 1)), limit && i == a[pos]);

    if (!limit) f[pos][sum] = ans;
    return ans;
}

inline int calc(int x) {
    memset(f, -1, sizeof f);
    int al = 0;
    while (x) a[++al] = x % 10, x /= 10;
    return dfs(al, 0, true);
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);

    int T;
    int cnt = 1;
    cin >> T;

    while (T--) {
        int x, y;
        cin >> x >> y;
        fa = F(x);
        printf("Case #%d: %d\n", cnt++, calc(y));
    }
    return 0;
}

但很不幸运,Time Limit Exceeded

五、释放减法的力量

大神原文

某大神写到:刚开始一眼看到这题,算了一下 F(999999999)=4599,也不是很大,感觉挺简单的,想着dp[pos][sum]不就完事了:sum记录从len(最高位)到pos位上累计的符合条件数字个数;

写出来交了一发,TLE,感觉就有点奇怪,翻了翻网上的题解,说是不能是dp[pos][sum],要是dp[pos][F(A)sum]才行;

也就是说,新定义dp[pos][comp]comp代表剩下的从第pos位到第1位,累加起来的权重,不能超过comp

这是为什么呢?原因其实很简单:

我们定义dp[pos][sum]的话,显然我们这 T组数据……

每组数据只要B有变化,由于sum是从B最高位(al)往下累加权重,它跟B有密切关系(B的长度al即为最高位),那么dp数组就必须要重新memset(dp,1,sizeof(dp)),这样才不会出错;

那么我们如果是dp[pos][comp]呢,comp代表从第1位到第pos位最多还能累加起多少权重,那么它就和B没有关系,我们就不需要在输入每组数据后都重新将dp数组全部重置为1

#include <cstdio>
#include <cstring>
#include <iostream>
#include <cmath>

using namespace std;
const int N = 32;       //数位的长度上限
const int M = 1024 * 10 + 10; //剩余的数大小(状态集合按这个划分)
int f[N][M];            //结果DP数组
int a[N];               //数位拆分出来的数组
int fa;
//利用递归计算出
int F(int x) {
    if (x == 0) return 0;
    return F(x / 10) * 2 + x % 10;
}

/**
 * @param pos   数位位置
 * @param st    剩余对比值,初始值是F(x)
 * @param limit 是不是贴上界
 * @return
 */
int dfs(int pos, int st, bool limit) {
    if (pos == 0) return st >= 0; //如果到达最后,并且有剩余,表示f(i)<= f(x),计数++
    if (st < 0) return 0;         //中途或最后一旦发生小于0情况,剪枝
    if (!limit && ~f[pos][st]) return f[pos][st];
    int ans = 0;
    int up = limit ? a[pos] : 9;
    for (int i = 0; i <= up; i++)
        //st = st - i * 2^ ( p-1 )
        ans += dfs(pos - 1, st - i * (1 << (pos - 1)), limit && i == a[pos]);

    if (!limit) f[pos][st] = ans;
    return ans;
}

inline int calc(int x) {
    int al = 0;
    while (x) a[++al] = x % 10, x /= 10;
    return dfs(al, fa, true);
}

int main() {
    //不加78MS,加了31MS,效果还是很明显的
    ios::sync_with_stdio(false);
    cin.tie(0);

    int T;
    int cnt = 1;
    cin >> T; //这个数值太BT了,最大10000次!

    //优化的写法
    memset(f, -1, sizeof f);
    while (T--) {
        int x, y;
        cin >> x >> y;
        fa = F(x); //计算出F(x)值,这是一个固定值
        printf("Case #%d: %d\n", cnt++, calc(y));
    }
    return 0;
}
posted @   糖豆爸爸  阅读(128)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!
Live2D
点击右上角即可分享
微信分享提示