hdu HDU 3555 Bomb

来源:互联网 发布:airx 知乎 编辑:程序博客网 时间:2024/05/16 01:14

题目描述:题目链接

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
3150500
 

Sample Output
0115
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
原创粉丝点击