1. 问题描述:
在所有的N位数中,存在多少个数字有偶数个数字3
输入
输入一个正整数N(N<=1000)
输出
输出有多少个数中有偶数个数字3,答案对12345取模
样例输入
2
样例输出
73
来源:http://oj.ecustacm.cn/problem.php?id=1113
2. 思路分析:
① 分析题目可以知道如果使用模拟计算出每个数字有多少个3的话那么肯定会造成数字溢出和超时的情况,因为N的数据规模最大是1000,所以使用这些模拟的方法是解决不了的,对于这种数据规模大的而且最后只是求解出一个数字的结果的问题不妨多往动态规划方面想想,动态规划一般是由前面的状态推导出当前的状态,而对于这道题目来时来说怎么样使用前面的状态推导出后面的状态呢,考虑到位数这个变量,我们可以这样想:从位数小的N位数字开始往位数比较大的N位数字进行推导,并且还需要注意到的另外一个限制是需要偶数个3,每一位上的数字都是可以由0-9组成(除了首位不能够由0组成除外的情况)所以我们可以这样想,应该声明一个二维的列表(因为使用的是python语言所以声明二维列表,其他语言可以声明int整型数组),dp[i][0]表示前i位数字中偶数个3的情况,dp[i][1]表示前i为数字为奇数个3的情况,为什么要声明二维列表呢?因为对于当前的位数i存在偶数个3的情况可以由前面i - 1位上偶数3的情况乘以9(当前的第i位数字为0,1,2,4,5,6,7,8,9九个数字)加上前面 i -1位上奇数的情况 * 1(第i位是3),对于奇数个3的情况也是类似的。所以要在声明两维的列表,第二维用来记录为奇数还是偶数的情况,所以在推导的时候需要记录前面状态是奇数还是偶数的情况。
② 其实这道题目也属于组合型的问题,前面i - 1位的情况有多少个,而当前第i位可以选择的情况有多少个,两种情况相乘起来那么表示的就是当前i位存在的情况,举出简单的例子那么就很容易理解了,比如N = 2的时候怎么样推导出N = 3的时候的情况,核心点是理解从长度较短的n位数字推导出长度较长的n位位置(数据规模小往除数据规模大的方向推导),一直递推到目标的N位数字。因为为了避免首位是0的情况的处理,所以在循环中使用N = 3开始推导,因为最终结果是对12345取余,所以我们在计算出当前的dp列表值之后对12345进行取余,这样可以避免溢出的情况,递推公式为:
dp[i][0] = (dp[i - 1][1] + dp[i - 1][0] * 9) % 12345
dp[i][1] = (dp[i - 1][0] + dp[i - 1][1] * 9) % 12345
③ 因为当前的两个状态只与前一个状态有关,没有使用到前面几个状态,所以可以进行空间上的优化,使用两个变量来代替二维dp列表进行递推,空间的优化一般是写出d列表的推导之后如果只利用到前面一个状态那么就可以进行空间上的优化
其实这种动态规划的题目需要多写题目,多思考,多总结再遇到类似题目的时候才会有条件反射
3. 代码如下:
if __name__ == '__main__':
N = int(input())
dp = [[0] * 2 for i in range(N + 2)]
dp[1][1], dp[1][0], dp[2][1], dp[2][0] = 1, 9, 17, 73
# 从n = 3的时候开始递推可以避免一开始的时候首位为0的情况
for i in range(3, N + 1):
dp[i][0] = (dp[i - 1][1] + dp[i - 1][0] * 9) % 12345
dp[i][1] = (dp[i - 1][0] + dp[i - 1][1] * 9) % 12345
print(dp[N][0])
优化:
if __name__ == '__main__':
# 因为考虑到当前状态只与前面一个状态有关所以可以进行空间的优化, 使用两个变量代替列表即可
N = int(input())
if N == 1: print(9)
elif N == 2: print(73)
else:
odd, even = 17, 73
for i in range(3, N + 1):
t1, t2 = odd, even
odd = (t2 + t1 * 9) % 12345
even = (t1 + t2 * 9) % 12345
print(even)