http://acm.hdu.edu.cn/showproblem.php?pid=3555
题意:问从0到n,有多少个数含有49。
分析:集训的时候做的,没来及写解题报告,刚好这两天又写了几个数位dp,顺带补了。
dp[i][0]表示长度为i的数串,含49的个数。
dp[i][1]表示长度为i的数串,不含49,且第一位为9的个数。
dp[i][2]表示长度为i的数串,不含49,且第一位不为9的个数。
递推过程见代码。
然后对于给定的n,从高位往下枚举,已经枚举过的位默认为该位最大值。当前位最大值为x,我们就算填0..x-1的方案,首先有x*dp[i][0],即这位填0..x-1乘以之后位含49的方案。如果这一位大于4,我们可以填4,下一位填9。如果这位和之前一位组成了49,那么之后0..之后最大值都是含49的,加上就可以。
网上还有一种类似做法,大同小异,不过更好理解。递推长度为i,含49,不含49,不含49且首位为9的个数。也从高到低枚举,首先加上这位任填,之后有49的方案数,如果之前已经有49了,那么这位任填,之后不含49的也可以加上。如果之前没有49,可以加上这位填4,之后补一个9的方案数。
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 using namespace std; 5 6 int T; 7 int a[40]; 8 long long dp[40][3]; 9 long long n; 10 int main() 11 { 12 memset(dp, 0, sizeof(dp)); 13 dp[0][2] = 1; 14 for (int i = 1; i <= 24; i++){ 15 dp[i][0] = dp[i-1][0] * 10 + dp[i-1][1]; //i-1长度有49,第i位随便填,或者在i-1且最高位为9的前面补一个4 16 dp[i][1] = dp[i-1][1] + dp[i-1][2]; //i-1不含49的两种加起来,都是在前面补9 17 dp[i][2] = dp[i-1][1] * 8 + dp[i-1][2] * 9; //i-1最高位为9,则这位不能填4和9,不为9,不能填9 18 } 19 scanf("%d", &T); 20 while(T--) 21 { 22 scanf("%I64d", &n); 23 int len = 0; 24 while(n){ 25 a[len++] = n % 10; 26 n = n / 10; 27 } 28 a[len] = 0; 29 long long ans = 0; 30 for (int i = len-1; i+1; i--){ 31 if (a[i] == 0) continue; 32 ans = ans + dp[i][0] * a[i]; 33 if (a[i] > 4) ans = ans + dp[i][1]; 34 if (a[i+1] == 4 && a[i] == 9){ 35 long long tmp = 0; 36 for (int j = i-1; j+1; j--) 37 tmp = tmp * 10 + a[j]; 38 ans = ans + tmp + 1; 39 break; //漏掉break就会重复计算了 40 } 41 } 42 printf("%I64d\n", ans); 43 } 44 return 0; 45 }