今天去牛客网看了看 包含一 这道题,一开始没看清,以为它要统计 1~n 所有数中数字 ‘1‘ 出现的总次数,也就是说,若 n == 11,则 ans = 4;而按照题目的原意 ans 应该为 3。看错题意后还是挣扎了好久,具体的调试过程也不想回忆叙述了,先贴上按照我一开始理解的意思的代码吧,虽然没有题目让我测,但我和自己写的暴力法对拍过,应该没问题的。
1 #include<cstdio> 2 #include<cstring> 3 #include<vector> 4 #include<algorithm> 5 using namespace std; 6 typedef long long LL; 7 8 LL C[12][12], p9[12] = {1,}; 9 // C 数组为组合数,p9 是 9 的幂数组 10 inline void initC(const int &n = 11) { 11 for(int i = 0; i <= n; ++i) 12 C[i][0] = 1; 13 for(int i = 1; i <= n; ++i) 14 for(int j = 1; j <= n; ++j) 15 C[i][j] = C[i-1][j-1] + C[i-1][j]; 16 for(int i = 1; i <= n; ++i) 17 p9[i] = p9[i-1] * 9; 18 } 19 20 LL all[16], num0[16], num1[16]; 21 // all[i] 表示 i 位数的个数,num0[i] 表示不含数字 1 的 i 位数的个数 22 // num1[i] 表示 i 位数中含有数字 1 出现的总次数,注意是 ‘1‘ 出现的总次数!求法有点麻烦 23 inline void init(const int &k = 11) { 24 all[0] = 1; 25 num0[0] = 1; 26 for(int i = 1; i <= k; ++i) { 27 all[i] = all[i-1] * 10; 28 num0[i] = num0[i-1] * 9; 29 } 30 initC(); 31 for(int n = 1; n <= k; ++n) { 32 LL &sum = num1[n]; 33 sum = 0; 34 for(int i = 1; i <= n; ++i) 35 sum += i * C[n][i] * p9[n-i]; 36 } 37 } 38 39 // 和数位 dp 的分析步骤有点类似 40 inline LL solve(const LL &n) { 41 LL digit[12], len = 0, x = n; 42 while(x) { 43 digit[++len] = x % 10; 44 x /= 10; 45 } 46 int count = 0; // 统计前 i 位数字 1 的个数 47 LL sum = 0; 48 // 核心计数部分(结合曾经做过的数位 dp 的思路来考虑) 49 for(LL i = len; i > 0; --i) { 50 sum += digit[i] * num1[i-1]; // 先加上当第 i 位取 0~digit[i]-1 时,i-1 位数的数字 1 的总个数 51 if(count) sum += count * digit[i] * all[i-1]; // 统计前面的 1 的个数(第 i 位后取任何数字都没所谓) 52 if(digit[i] == 1) ++count; // 计数器加 1 53 if(digit[i] > 1) sum += all[i-1]; // 统计若当前位取 1 时的个数(同理后面是什么都没所谓) 54 } 55 // 好好体会下这个数位 dp 的思路,要做到不重不漏真不容易~ 56 return sum; 57 } 58 59 // 分解统计 ‘1‘ 的个数 60 inline LL caclu(LL x) { 61 LL res = 0; 62 while(x) { 63 if(x % 10 == 1) ++res; 64 x /= 10; 65 } 66 return res; 67 } 68 69 // 暴力枚举统计 70 inline LL test(const LL &x) { 71 LL sum = 0; 72 for(LL i = 1; i <= x; ++i) 73 sum += caclu(i); 74 return sum; 75 } 76 77 int main() { 78 LL n; 79 init(); 80 while(~scanf("%I64d",&n)) 81 printf("solve = %I64d test = %I64d\n\n",solve(n+1),test(n)); 82 return 0; 83 }
统计 1~n 中数字‘1‘出现的总次数
后来,按照题目的意思我又重做了一遍:
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 using namespace std; 5 6 int dp[3][16]; 7 // dp[0][i] 表示包含 1 的 i 位数的个数 8 // dp[1][i] 表示以 1 开头的不包含 1 的 i 位数的个数 9 // dp[2][i] 表示不包含 1 的 i 位数的个数 10 // 其实 dp[1][i] 和 dp[2][i] 实质是一样的,合并成一个就行了,所以空间和时间都能提升一点;为了让它更直观,我就不改了 11 inline void init(int n = 11) { 12 dp[2][0] = 1; 13 for(int i = 1; i <= n; ++i) { 14 dp[0][i] = 10 * dp[0][i-1] + dp[2][i-1]; 15 dp[1][i] = dp[2][i-1]; 16 dp[2][i] = dp[2][i-1] * 9; 17 } 18 } 19 20 inline int solve(int x) { 21 int digit[12], len = 0; 22 while(x) { 23 digit[++len] = x % 10; 24 x /= 10; 25 } 26 bool flag = 0; 27 int sum = 0; 28 for(int i = len; i; --i) { 29 sum += digit[i] * dp[0][i-1]; 30 if(flag) sum += digit[i] * dp[2][i-1]; 31 else if(digit[i] > 1) sum += dp[1][i]; 32 if(digit[i] == 1) flag = 1; 33 } 34 return sum; 35 } 36 37 const int inf = 0x7fffffff; 38 39 int main() { 40 int n; 41 init(); 42 while(~scanf("%d",&n)) { // 有符号 int 的上限,要注意处理好 43 if(n == inf) printf("%d\n",solve(n) + 1); 44 else printf("%d\n",solve(n+1)); 45 } 46 return 0; 47 }
统计包含‘1‘的数字的个数
耗费了一个中午和下午时间写完这两个代码后,我发觉我对于数位 dp 已经感到无爱了,再给我来一道这样的题的话就真的要挂了~
---------------------------------------------来填一下坑先---------------------------------------------------
后来发现,原来还有这样的一道题 统计一,把我第一个代码的 I64d 改为 lld 以及输出修改一下就能过,果然我的做法是对哦~
数位dp——统计'1'的个数
时间: 2024-11-07 18:45:47