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
Now the counter-terrorist knows the number N. They want to know the final points of the power. Can you help them?
The input terminates by end of file marker.
3150500
0115HintFrom 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)); }}
- hdu HDU 3555 Bomb
- hdu 3555 Bomb
- hdu 3555 Bomb
- HDU 3555 Bomb
- hdu 3555 Bomb
- HDU:3555 Bomb
- HDU 3555 Bomb
- hdu 3555 Bomb
- hdu 3555 Bomb
- hdu 3555 Bomb
- hdu 3555 Bomb
- HDU-3555 Bomb
- hdu-3555-Bomb
- HDU 3555 Bomb
- HDU 3555 Bomb [数位]
- HDU 3555 Bomb
- HDU 3555 Bomb
- HDU 3555 Bomb
- MySql的安装和使用
- C#中的三大结构
- 【十四】两个成员的类模板
- 搜索关键字变颜色
- PYTHON_DAY_12_20160610
- hdu HDU 3555 Bomb
- 关于linearLaout的权重,随手笔记
- 高斯模糊的算法
- PYTHON_DAY_13_201606011
- Copy List with Random Pointer 带随机指针的链表的拷贝
- 【每天学一点】C++基础5
- Android自定义ListView
- 一个例子带你深入理解ViewPager之广告轮播条的实现
- Java对象初始化