hdu HDU 3555 Bomb

题目描述:题目链接

Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 131072/65536 K (Java/Others)
Total Submission(s): 13265    Accepted Submission(s): 4765


Problem Description
The counter-terrorists found a time bomb in the dust. But this time the terrorists improve on the time bomb. The number sequence of the time bomb counts from 1 to N. If the current number sequence includes the sub-sequence "49", the power of the blast would add one point.
Now the counter-terrorist knows the number N. They want to know the final points of the power. Can you help them?
 

Input
The first line of input consists of an integer T (1 <= T <= 10000), indicating the number of test cases. For each test case, there will be an integer N (1 <= N <= 2^63-1) as the description.

The input terminates by end of file marker.
 

Output
For each test case, output an integer indicating the final points of the power.
 

Sample Input
  
  
3 1 50 500
 

Sample Output
  
  
0 1 15
Hint
From 1 to 500, the numbers that include the sub-sequence "49" are "49","149","249","349","449","490","491","492","493","494","495","496","497","498","499", so the answer is 15.

解题思路:

接触到有数位dp这样的东西,就搜出这道题,说实话,并没有觉得这道题好做,下面好好分析下题目:


这道题要求出含有49子串的数字的个数,如果想去寻找规律,则会很容易陷入各种条件判断,稍有不慎就WA了,因此在接连碰壁后打算寻找其他做法,首先最容易想到的是暴力遍历,可是题目中N的大小达到了2^63-1的数量级,也就是10^18到10^19这样的数量级,然后每次循环都得判断这个数中是否含有49字串,而且这里面有很多重复判断,例如对于1000,当循环到了49和149时,都得重复判断低二位是否含有49,并且对于多测试样例,更加剧这种重复。

这时,就可以利用数位dp,也即是先不管具体上下限,先单纯考虑不同位数下,出现49的次数,用dp[i]来表示i位数能有多少个数字有49子串,但这样是不够的,因此考虑如下:

dp[i][0] 表示i位数不含有49的个数

dp[i][1]  表示i位数不含有49,且以9开头的个数(这样多加一位的时候,如果以4开头的话,则含有49)

dp[i][2]  表示i位数含有49的个数

则可以得到下面的状态转移方程:

dp[i][0] = dp[i-1][0] * 10 - dp[i-1][1]  减去第i位为4,其余i-1位为以9开头的不含49的数的个数

dp[i][1] = dp[i-1][0]  等于第i位为9,其余i-1位是不含49的个数

dp[i][2] = dp[i-1][2] * 10 + dp[i-1][1]  加上第i位是4,其余i-1位是以9开头的不含49的数的个数


然后是初始条件:

dp[0][0] = 1  假设位数是0的不含49的个数为1个,

dp[0][1] = dp[0][2] = 0  位数为0的含49个数为0

这样初始化的话就能满足上面条件,


这样就可以很容易求出位数是20以内这三种情况的个数,接下来的问题就是把这个结果用到这道题中,这也是挺复杂的一件事:

可以从高位到低位扫描所给的数,例如:

第 i 位是 k (k为0到9的数),则 i 位的情况有0,1,2,....,k,首先考虑0,1,...,k-1的情况,共k种,则结果含有 k * dp[i-1][2] 个含49的个数,

然后考虑第 i 位是 k 的情况,假设 i 位是 k,也就是第 i 位固定下来,接下来只需要考虑余下的 i-1位的情况,

但这样考虑还有不足,当在第 i 位之前已经有出现49的时候,那么结果还需要加上余下 i-1不含有49的情况,因为整个数已经包含了49的情况,

如果第i 位之前没有出现 49,那么只要第 i 位的数字大于4,也就是 k > 4, 那么 k-1 >= 4,所以当第 i 位是4的时候,还可以加上以9开头的不含有49的情况。

但是。。。这样还是不够的。。。

考虑这样的情况,当最后两位恰好是49的时候,前面的考虑并没有考虑进去,但这并非简单的判断如果最后两位是49就加1这么简单,因为如果在第二位的前面出现过49,那么这种情况已经包含进去了,如果第二位的数字大于4,那么这种情况也包含进去了,为了避免复杂且重复的判断,这里直接将输入的N值加1。


#include <cstdio>
#include <iostream>
#include <cstring>
using namespace std;

long long dp[21][3];
void init(){
    memset(dp, 0, sizeof(dp));
    dp[0][0] = 1;
    for(int i = 1; i < 21; i++){
        dp[i][0] = dp[i-1][0] * 10 - dp[i-1][1];
        dp[i][1] = dp[i-1][0];
        dp[i][2] = dp[i-1][2]*10 + dp[i-1][1];
    }
}

int main(){
    //freopen("t.txt", "r", stdin);
    init();

    int t;
    scanf("%d", &t);
    long long n;
    while(t--){
        scanf("%lld", &n);
        n++;

        int numArray[22], len = 0;
        memset(numArray, 0, sizeof(numArray));
        while(n){
            numArray[++len] = n % 10;
            n = n / 10;
        }

        long long res = 0;
        int pre = -1;
        int flag = 0;
        for(int i = len; i >= 1; i--){
            res += dp[i-1][2] * numArray[i];
            if(flag)
                res += dp[i-1][0] * numArray[i];
            else if(numArray[i] > 4)
                res += dp[i-1][1];

            if(pre == 4 && numArray[i] == 9)
                flag = 1;
            pre = numArray[i];
        }
        printf("%lld\n", res);
    }
    return 0;
}

不难看出,除了前面求dp数组外(这部分可以看做常数时间,虽然对于一些小数据,它可能会成为整个程序最耗时的地方,因为它把不需要用到的位也求出来了),其余部分的时间复杂度为O(n),这里的n指输入数据的位数!!所以说效率是很快的,在hdu中的运行时间为15MS。


下面介绍一种采用dfs 递归的方法,为了与上面的做对比,这里先说明下它的时间复杂度为O(n),注意,这里的n指输入的数据,在hdu上运行的时间为31MS,但自己用一些小数据测试却是比上面的做法快,原因是他避免了对不需要用到的位数进行求解的过程。

其中,dp[i][0]表示长度为i,不以9结尾(也就是新加入的高位值不能为4)的满足条件的数的个数,

dp[i][1]表示长度为i,以9结尾(也就是新加入的高位值为4)的满足条件的数的个数;(注:dp存的值都是在没有上下限约束条件下的,也即只考虑位数)

此外,程序最主要的部分是一个dfs函数:

long long dfs(int pos, bool preIs4, bool hasCeiling)
用来从高位到低位进行dfs,返回在输入的n的0到pos位能够表示满足条件的个数,其中preIs4表示在第pos+1位的数是否为4,hasCeiling表示比pos高位的数是否都已经到达n的对应位的值,也即比pos高的位是否已经遍历完成。

#include <cstdio>
#include <cstring>
using namespace std;

long long dp[22][2];
long long cnt[22] = {1};
int numArray[22];
long long n;

void init(){
    memset(dp, -1, sizeof(dp));
    for(int i = 1; i < 21; i++)
        cnt[i] = cnt[i-1] * 10;
}

long long dfs(int pos, bool preIs4, bool hasCeiling){
    if(pos == 0)
        return 0;
    if(!hasCeiling && dp[pos][preIs4] >= 0)
        return dp[pos][preIs4];

    int upperBound = hasCeiling ? numArray[pos] : 9;
    long long res = 0;

    for(int i = 0; i <= upperBound; i++){
        if(preIs4 && i == 9)
            res += hasCeiling ? (n % cnt[pos-1] + 1) : cnt[pos-1];
        else
            res += dfs(pos-1, i == 4, hasCeiling && i == numArray[pos]);
    }

    //注意:dp中保留的是在没有任何上下界约束条件下的值
    if(!hasCeiling)
        dp[pos][preIs4] = res;

    return res;
}

int main(){
    //freopen("t.txt", "r", stdin);

    int t;

    init();

    scanf("%d", &t);
    while(t--){
        scanf("%lld", &n);
        int len = 0;
        long long tmp = n;
        while(tmp){
            numArray[++len] = tmp % 10;
            tmp = tmp / 10;
        }
        printf("%lld\n", dfs(len, false, true));
    }
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
提供的源码资源涵盖了小程序应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 适合毕业设计、课程设计作业。这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。 所有源码均经过严格测试,可以直接运行,可以放心下载使用。有任何使用问题欢迎随时与博主沟通,第一时间进行解答!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值