题解 CF837D Round Subset
省选前来水题水题解
首先一步简单的转化是把一个数末尾 \(0\) 的个数变成这个数的质因数分解中 \(2\) 的次数和 \(5\) 的次数的最小值。
乘法对于幂就是加法,于是题目变成了:
给你 \(n\) 个物品,每个物品有两个属性 \(a_i,b_i\),你需要从中选出 \(k\) 个(设选出的是 \(p_1,p_2\cdots,p_k\)),令 \(A=\sum_{i=1}^ka_{p_i},B=\sum_{i=1}^kb_{p_i}\),你需要最大化 \(\min\{A,B\}\)。
这是个经典的背包问题,我们可以设 \(f_{i,j,k,m}\) 表示前 \(i\) 个物品里选了 \(j\) 个,\(a\) 的和是 \(k\),\(b\) 的和是 \(m\) 是否可行,转移就和普通的背包一样。这样的话状态太多了,过不去。
考虑优化:注意到我们最终的答案是 \(\min\{A,B\}\),那也就是说,在 \(A\) 一定的时候,\(B\) 越大越好。
所以我们把 \(b\) 的和放进价值这一栏(\(dp\) 值),把状态改为 \(f_{i,j,k}\),表示前 \(i\) 个物品里面选 \(j\) 个,\(a\) 的和是 \(k\) 时 \(b\) 的和的最大值。
转移:
\[f_{i,j,k}\gets \max\{f_{i-1,j,k},f_{i-1,j-1,k-a_i}+b_i\}
\]
令 \(v=\sum_{i=1}^n a_i\),时间复杂度为 \(\mathcal{O}(n^3\log_5 v)\),空间可以滚动数组滚掉第一维(注意循环要倒着循环保证每个物品只选一次),空间复杂度为 \(\mathcal{O}(n^2\log_5 v)\)。
代码如下:
#include <cstdio>
#include <cstring>
#include <cctype>
#include <iostream>
#include <algorithm>
#include <queue>
using namespace std;
template <typename T>
inline T read() {
T num = 0; char c = getchar();
while (!isdigit(c)) c = getchar();
while (isdigit(c)) num = (num << 1) + (num << 3) + (c ^ 48) ,c = getchar();
return num;
}
const int N = 205 ,LG2 = 64 ,LG5 = 26 ,INF = 0x3f3f3f3f;
int f[N][N * LG5] ,n ,k;
signed main() {
n = read <int>() ,k = read <int>();
const int ml5 = n * LG5;
for (int i = 0; i <= k; i++)
for (int j = 0; j <= ml5; j++)
f[i][j] = -INF;
f[0][0] = 0;
for (int i = 1; i <= n; i++) {
long long x = read <long long>();
long long tx = x;
int a = 0 ,b = 0;
while (tx % 2 == 0) tx /= 2 ,a++;
tx = x;
while (tx % 5 == 0) tx /= 5 ,b++;
for (int j = min(k ,i); j >= 1; j--)
for (int l = ml5; l >= b; l--)
f[j][l] = max(f[j][l] ,f[j - 1][l - b] + a);
}
int ans = 0;
for (int i = 1; i <= ml5; i++) ans = max(ans ,min(f[k][i] ,i));
printf("%d\n" ,ans);
return 0;
}

题解 CF837D Round Subset
浙公网安备 33010602011771号