AcWing 4081. 选数
选数
题意
给定 \(n\) 个整数 \(a_1, a_2, \ldots a_n\) 。
请你从中选去 恰好 \(k\) 个数字,要求选出来的数字的乘积的末尾的 \(0\) 尽可能多。
请输出末尾 \(0\) 的最大可能数量。
\(1 \le n \le 200, 1 \le k \le n, 1 \le a_i \le 10^{18}\) 。
分析
对于所有的质因数,只有 \(2 \times 5\) 才会产生末尾 \(0\) 。
选择 \(k\) 个数字,假设乘积有 \(a\) 个 \(2\) ,\(b\) 个 \(5\) 。那么产生的末尾 \(0\) 一共 \(min(a, b)\) 个。
所以要求 \(min(a, b)\) 的最大值。
设 \(f(i, j, k)\) 表示前 \(i\) 个数字,选择 \(j\) 个数字,并且 \(5\) 的个数为 \(k\) 时,\(2\) 的最大数量。
转移方程:\(f(i, j, k) = max(f(i, j, k), f(i-1, j-1, k-c_5) + c_2)\) 。
其中 \(c_5\) 表示第 \(i\) 位 \(5\) 的数量,\(c_2\) 表示第 \(i\) 位 \(2\) 的数量。
看作二维的 \(01\) 背包问题,\(c_5\) 为体积, \(c_2\) 为价值。
由于只用到了前一层,可以把第一维压掉。
Code
#include <iostream>
#include <cstring>
using namespace std;
const int N = 210;
int f[N][30 * N + 10];
signed main ()
{
int n, k; cin >> n >> k;
memset(f, -0x3f, sizeof f);
f[0][0] = 0;
for (int i = 1; i <= n; i ++ )
{
long long x; cin >> x;
int c2 = 0, c5 = 0;
while(x % 2 == 0) { ++ c2; x /= 2; }
while(x % 5 == 0) { ++ c5; x /= 5; }
// 体积为c5,价值为c2
for (int j = k; j >= 1; j -- ) // 选j个数字
for (int r = j * 25; r >= c5; r -- )
f[j][r] = max(f[j][r], f[j-1][r - c5] + c2);
}
int ans = 0;
for (int i = 0; i <= 25 * N; i ++ ) ans = max(ans, min(i, f[k][i]));
cout << ans << endl;
return 0;
}