首页 > 代码库 > hdu3555 Bomb (记忆化搜索 数位DP)

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
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.
 
Author
fatboy_cw@WHU
 
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的这一位是46         f[i][1] = f[i-1][0];                    ///开头是9的 = 之前没49的这1位是97         f[i][2] = (ll)f[i-1][2]*10 + f[i-1][1]; ///有49的 = 之前有49的这一位随便 + 之前开头是9的这一位是48     }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 long10 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 }
View Code


数位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 long10 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的这一位是438         f[i][1] = f[i-1][0];                    ///开头是9的 = 之前没49的这1位是939         f[i][2] = (ll)f[i-1][2]*10 + f[i-1][1]; ///有49的 = 之前有49的这一位随便 + 之前开头是9的这一位是440     }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 }
View Code