1. 问题描述:
给定 n 个整数 a1,a2,…,an。请你从中选取恰好 k 个数,要求选出的数的乘积的末尾 0 的数量尽可能多。请输出末尾 0 的最大可能数量。
输入格式
第一行包含两个整数 n,k。第二行包含 n 个整数 a1,a2,…,an。
输出格式
一个整数,表示末尾 0 的最大可能数量。
数据范围
前 6 个测试点满足 1 ≤ n,k ≤ 10。
所有测试点满足 1 ≤ n ≤ 200,1 ≤ k ≤ n,1 ≤ ai ≤ 10 ^ 18。
输入样例1:
3 2
50 4 20
输出样例1:
3
输入样例2:
5 3
15 16 3 25 9
输出样例2:
3
输入样例3:
3 3
9 77 13
输出样例3:
0
来源:https://www.acwing.com/problem/content/description/4084/
2. 思路分析:
由题目可知这道题目属于经典的有限制的选择问题,也叫做背包问题,这两个问题是等价的,一般是从集合中选择一个子集,这个子集需要满足某些条件最终需要使得子集的分值最大或者求解有对应的方案数目;因为是背包模型所以我们需要将题目的各种限制放到状态中(抽象出体积,重量和价值这些),因为恰好选择k个数所以将k看成是背包的容量,因为最终需要使得0的数量最多,所以需要尽量包含因子2和因子5,并且因子2和因子5的数量独立的,所以可以将因子2的数量或者因子5的数量放到状态中,这里将因子2或者因子5数量看成是重量的限制,因为需要将其放到状态中所以最终因子2或者因子5的数量需要计算到时间和空间复杂度内,因子5的数量肯定小于因子2的数量的,ai最大是10 ^ 18,所以因子2的数量最多是60个,因子5的数量最多有25个,所以将因子5的数量放到状态中,这里将因子5的数量看成重量的限制,因子2的数量看成是价值,这样就可以将其转化为二维费用的零一背包问题,其中dp[i][j]表示恰好选择i个数中有j个5的最多的因子2的数目,使用两维费用的零一背包问题的模板即可,因为最多有200个数每一个数最多有25个因子5所以第二维的长度为5010,因为题目中规定"恰好"选择k个数,对于"恰好"的背包问题,与"最多"...的区别主要是在初始化dp数组初始值不一样,对于"恰好"的背包问题,第一个状态是合法的,其余状态都是不合法的,这样可以确保最终状态一定是从f(0,0)递推过来的状态,因为求解的是最大值所以将其初始化为负无穷,也即f(0,0) = 0,最后枚举一下恰好选择k个数中1个5,2个5...中的最大值即可,并且在枚举重量限制的时候可以优化一下常数,从25 * i开始逆序枚举,因为当前i个数最多25 * i个因子5,常数优化一下效率会高一点。
3. 代码如下:
class Solution:
def process(self):
M = 5010
n, m = map(int, input().split())
a = [0] + list(map(int, input().split()))
# v[i]表示第i个数因子2的数量, w[i]表示第i个数因子5的数量
v, w = [0] * (n + 10), [0] * (n + 10)
INF = 10 ** 10
# dp[i][j]表示恰好i个数有j个5的最多因子2的数目
dp = [[-INF] * M for i in range(m + 10)]
for i in range(1, n + 1):
x = a[i]
# 计算x中因子2和因子5的数量
while x % 2 == 0:
v[i] += 1
x //= 2
while x % 5 == 0:
w[i] += 1
x //= 5
# 因为是恰好所以初始化的时候第一个状态是合法的其余状态都是不合法的
dp[0][0] = 0
# 逆序枚举这样可以压缩一维, 类似于之前背包问题的优化
for i in range(1, n + 1):
for j in range(m, 0, -1):
# 下面不是从M枚举到w[i]而且从25 * i枚举到i, 其实是常数优化
for k in range(25 * i, w[i] - 1, -1):
dp[j][k] = max(dp[j][k], dp[j - 1][k - w[i]] + v[i])
res = 0
for i in range(1, M):
# 枚举当前有m个数字有i个5的时候dp[m][i]个2的时候的最大值
# 每一组取决于因子2, 5的最小值, 在所有情况中取一个最大值即可
res = max(res, min(i, dp[m][i]))
return res
if __name__ == '__main__':
print(Solution().process())