hdu3555 Bomb (记忆化搜索 数位DP)

http://acm.hdu.edu.cn/showproblem.php?pid=3555

Bomb

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

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.

Author

[email protected]

Source

2010 ACM-ICPC Multi-University Training Contest(12)——Host by WHU

Recommend

zhouzeyong   |   We have carefully selected several similar problems for you:  3554 3556 3557 3558 3559

题意:输入n,输出1~n中含有“49”的整数数量。(1 <= N <= 2^63-1

题解:

数位DP 或 记忆化搜索。

n这么大,不可能枚举。不过我们先看看按位从高到低的枚举深搜:

 1 ll dfs(int w, int state, bool limit) {
 2     if(w<0) return state==2;
 3
 4     int maxi=limit ? a[w] : 9;
 5     ll re=0;
 6     for(int i=0; i<=maxi; i++) {
 7         int s=state;
 8         if(state==1) {
 9             if(i==9) s=2;
10             else if(i!=4)s=0;
11         } else if(state==0 && i==4) s=1;
12         re+=dfs(w-1, s, limit && i==a[w]);
13     }
14
15     return re;
16 }

ans=dfs(an-1,0,1)。其中state为0表示没49,state为1表示上一位是9,state为2表示有49。a[w]为n的第(w+1)位,n有an位。

可以看出这是一个非常裸的搜索,简直枚举每个数,肯定超时。

但仔细观察可以发现,有好多次递归都返回同样的结果。对,就是limit=0时,w和state固定的调用,都是返回0~w位的数随意取0~9,含有49的种类数。这样,我们可以用记忆化搜索,记录算好的f[w][state],下次调用的时候就不用算,直接返回值。

还有更快的方法,就是直接数位DP。这个实在是碉,我自己的话根本写不出来。

首先初始化还比较容易,像这样,生成的f[][]是和上面记忆化搜索的一样的:

1 void init(){
2     memset(f,0,sizeof(f));
3     f[0][0]=1;
4     for(int i=1;i<20;i++){
5         f[i][0] = (ll)f[i-1][0]*10 - f[i-1][1]; ///没49的 = 之前没49的这一位随便 - 之前开头是9的这一位是4
6         f[i][1] = f[i-1][0];                    ///开头是9的 = 之前没49的这1位是9
7         f[i][2] = (ll)f[i-1][2]*10 + f[i-1][1]; ///有49的 = 之前有49的这一位随便 + 之前开头是9的这一位是4
8     }
9 }

但是统计的时候就难了,是这样的:

 1 ll farm(ll x) {
 2     an=0;
 3     while(x) {
 4         a[an++]=x%10;
 5         x/=10;
 6     }
 7     a[an]=0;
 8     ll ans=0;
 9     bool flag=false;
10     for(int i=an-1;i>=0;i--){
11         ans+=(ll)f[i][2]*a[i];///后面有49,这一位取0~a[i]-1(取a[i]的情况在之后的循环中计算)
12         if(flag) ans+=(ll)f[i][0]*a[i];///前面全固定取a[i],有49了,这位0~a[i]-1,后面取没49的(后面有49的情况已经在上一句算过了)
13         else if(!flag && a[i]>4)///前面全固定取a[i],没49,这一位能取4,后面开头为9的情况数f[i][1],则就是组成49的情况数
14             ans+=f[i][1];
15         if(a[i]==9 && a[i+1]==4) flag=true;///这一位取a[i],上一位取a[i+1]时成49,标flag,日后计算
16     }
17     return ans;
18 }

其中for循环每层是固定比当前位更高的位为a[],即该位的边缘值,进行计算,因为那一位取不边缘的值的情况已经在那一位算过了,具体可以看上面的注释。

记忆化搜索全代码:

 1 //#pragma comment(linker, "/STACK:102400000,102400000")
 2 #include<cstdio>
 3 #include<cmath>
 4 #include<iostream>
 5 #include<cstring>
 6 #include<algorithm>
 7 #include<cmath>
 8 using namespace std;
 9 #define ll long long
10
11 ll f[20][3];///f[i][j],i位数,j=0是没49的,1是9开头的,2是有49的
12 int a[20],an;
13
14 ll dfs(int w, int state, bool limit) {
15     if(w<0) return state==2;
16     if(!limit && f[w][state]!=-1) return f[w][state];
17
18     int maxi=limit ? a[w] : 9;
19     ll re=0;
20     for(int i=0; i<=maxi; i++) {
21         int s=state;
22         if(state==1) {
23             if(i==9) s=2;
24             else if(i!=4)s=0;
25         } else if(state==0 && i==4) s=1;
26         re+=dfs(w-1, s, limit && i==a[w]);
27     }
28
29     if(!limit) f[w][state]=re;
30     return re;
31 }
32
33 ll farm(ll x) {
34     an=0;
35     while(x) {
36         a[an++]=x%10;
37         x/=10;
38     }
39     return dfs(an-1,0,1);
40 }
41
42 int main() {
43     int T;
44     ll n;
45     memset(f,-1,sizeof(f));
46     scanf("%d",&T);
47     while(T--) {
48         scanf("%I64d",&n);
49         printf("%I64d\n",farm(n));
50     }
51     return 0;
52 }

数位DP全代码:

 1 //#pragma comment(linker, "/STACK:102400000,102400000")
 2 #include<cstdio>
 3 #include<cmath>
 4 #include<iostream>
 5 #include<cstring>
 6 #include<algorithm>
 7 #include<cmath>
 8 using namespace std;
 9 #define ll long long
10
11 ll f[20][3];///f[i][j],i位数,j=0是没49的,1是9开头的,2是有49的
12 int a[20],an;
13
14 ll farm(ll x) {
15     an=0;
16     while(x) {
17         a[an++]=x%10;
18         x/=10;
19     }
20     a[an]=0;
21     ll ans=0;
22     bool flag=false;
23     for(int i=an-1;i>=0;i--){
24         ans+=(ll)f[i][2]*a[i];///后面有49,这一位取0~a[i]-1(取a[i]的情况在之后的循环中计算)
25         if(flag) ans+=(ll)f[i][0]*a[i];///前面全固定取a[i],有49了,这位0~a[i]-1,后面取没49的(后面有49的情况已经在上一句算过了)
26         else if(!flag && a[i]>4)///前面全固定取a[i],没49,这一位能取4,后面开头为9的情况数f[i][1],则就是组成49的情况数
27             ans+=f[i][1];
28         if(a[i]==9 && a[i+1]==4) flag=true;///这一位取a[i],上一位取a[i+1]时成49,标flag,日后计算
29     }
30     return ans;
31 }
32
33 void init(){
34     memset(f,0,sizeof(f));
35     f[0][0]=1;
36     for(int i=1;i<20;i++){
37         f[i][0] = (ll)f[i-1][0]*10 - f[i-1][1]; ///没49的 = 之前没49的这一位随便 - 之前开头是9的这一位是4
38         f[i][1] = f[i-1][0];                    ///开头是9的 = 之前没49的这1位是9
39         f[i][2] = (ll)f[i-1][2]*10 + f[i-1][1]; ///有49的 = 之前有49的这一位随便 + 之前开头是9的这一位是4
40     }
41 }
42
43 int main() {
44     int T;
45     ll n;
46     init();
47     scanf("%d",&T);
48     while(T--) {
49         scanf("%I64d",&n);
50         printf("%I64d\n",farm(n+1));///farm(x)是算小于x的
51     }
52     return 0;
53 }

hdu3555 Bomb (记忆化搜索 数位DP),布布扣,bubuko.com

时间: 2024-10-27 04:24:03

hdu3555 Bomb (记忆化搜索 数位DP)的相关文章

UVALive 4864 Bit Counting --记忆化搜索 / 数位DP?

题目链接: 题目链接 题意:如果一个数二进制n有k位1,那么f1[n] = k,如果k有s位二进制1,那么f2[n] = f1[k] = s.  如此往复,直到fx[n] = 1,此时的x就是n的”K值“,现在要求[L,R]内的”K值“为X的数有多少个.(1<=L<=R<=10^18) 解法:首先可以看到10^18最多只有61位左右的数,所以我们只需处理1~61之间每个数有多少个1,即可知道1~61之间每个数”K值“是多少. 然后就将求[L,R]之间的个数变成求[1,R]-[1,L-1]

poj 3156 hash+记忆化搜索 期望dp

#include<iostream> #include<cstdio> #include<algorithm> #include<cstring> using namespace std; int n,m; #define N 32 #define mod 10007LL #define mod2 10000007 #define inf 0x3fffffff typedef long long ll; typedef double dd; int f[N]

记忆化搜索(DFS+DP) URAL 1501 Sense of Beauty

题目传送门 1 /* 2 题意:给了两堆牌,每次从首部取出一张牌,按颜色分配到两个新堆,分配过程两新堆的总数差不大于1 3 记忆化搜索(DFS+DP):我们思考如果我们将连续的两个操作看成一个集体操作,那么这个操作必然是1红1黑 4 考虑三种情况:a[]连续两个颜色相同,输出11:b[]连续两个相同,输出22: 5 a[x] != b[y], 输出12:否则Impossible 6 详细解释:http://blog.csdn.net/jsun_moon/article/details/10254

HDU 2476 String painter(记忆化搜索, DP)

题目大意: 给你两个串,有一个操作! 操作时可以把某个区间(L,R) 之间的所有字符变成同一个字符.现在给你两个串A,B要求最少的步骤把A串变成B串. 题目分析: 区间DP, 假如我们直接想把A变成B,那么我们DP区间的时候容易产生一个问题:假如我这个区间更新了,那么之前这个区间的子区间内DP出来的值就没用. 然后考虑到这里一直想不过去.最后看了看题解才知道. 我们可以先预处理一下怎么将一个空串变成B串需要的操作数. 这样我们就不用考虑子区间被覆盖的情况了. 如区间L,R 我们需要考虑的是点L是

硬币找零-记忆化搜索(DP动态规划)

硬币找零 时间限制:1000 ms  |  内存限制:65535 KB 难度:3 描述 在现实生活中,我们经常遇到硬币找零的问题,例如,在发工资时,财务人员就需要计算最少的找零硬币数,以便他们能从银行拿回最少的硬币数,并保证能用这些硬币发工资. 我们应该注意到,人民币的硬币系统是 100,50,20,10,5,2,1,0.5,0.2,0.1,0.05, 0.02,0.01 元,采用这些硬币我们可以对任何一个工资数用贪心算法求出其最少硬币数. 但不幸的是: 我们可能没有这样一种好的硬币系统, 因此

UVA 10626--Buying Coke+记忆化搜索+DP

题目链接:点击进入 原来定义状态dp[n][n1][n5][n10]表示购买n瓶可乐后剩余1,5,10分硬币n1,n5,n10个时花费硬币数最小的数量.然后状态转移是:1.8个一分硬币购买第n瓶可乐,t=dp[n-1][n1+8][n5][n10]+8; 2.一个五分和3个1分,t=dp[n-1][n1+3][n5+1][n10]+4; 3.两个5分t=dp[n-1][n1][n5+2][n10]; 4.一个10分,t=dp[n-1][n5][n10+1]. 对于第一二种dp[n][n1][n5

Codeforces Round #338 (Div. 2) B. Longtail Hedgehog 记忆化搜索/树DP

B. Longtail Hedgehog This Christmas Santa gave Masha a magic picture and a pencil. The picture consists of n points connected by m segments (they might cross in any way, that doesn't matter). No two segments connect the same pair of points, and no se

矩形嵌套-记忆化搜索(dp动态规划)

矩形嵌套 时间限制:3000 ms  |  内存限制:65535 KB 难度:4 描写叙述 有n个矩形,每个矩形能够用a,b来描写叙述,表示长和宽. 矩形X(a,b)能够嵌套在矩形Y(c,d)中当且仅当a<c,b<d或者b<c,a<d(相当于旋转X90度).比如(1,5)能够嵌套在(6,2)内,但不能嵌套在(3,4)中. 你的任务是选出尽可能多的矩形排成一行,使得除最后一个外,每个矩形都能够嵌套在下一个矩形内. 输入 第一行是一个正正数N(0<N<10).表示測试数据组

hdu 4597 Play Game (记忆化搜索 区间dp)

#include<cstdio> #include<cstring> #include<cmath> #include<iostream> #include<algorithm> using namespace std; int dp[30][30][30][30]; int vis[30][30][30][30]; int a[2][30],sum[2][30]; int dfs(int i,int j,int k,int l) { if(vi