http://acm.hdu.edu.cn/showproblem.php?pid=3555
BombTime 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. Output For each test case, output an integer indicating the final points of the power. Sample Input 3 Sample Output 0 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", 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