题解 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;
}
posted @ 2022-04-24 18:33  recollector  阅读(42)  评论(0)    收藏  举报