CF 837D Round Subset - DP
CF 837D Round Subset
题目链接:洛谷 CF 837D Round Subset CF 837D Round Subset
算法标签: DP
、思维
题目描述:
我们定义一个数的幸运值是这个数末尾 \(0\) 的个数,
给你一个长度为 \(n\) 的数列,在这个数列中选出来 \(k\) 个数,使得选出来的所有数的积的幸运值最大。
题解:
考场上 \(yy\) 贪心,卡过去两个点,后来改 DP RE7个点,我真棒(%……&#¥%&¥%……¥……*)
现在开始口胡正解:
DP
首先在看完题之后一定可以发现,这道题和每个数分解质因数之后 \(2\) 和 \(5\) 的因数个数有关系,之后考虑怎么做。
这道题考场上和旁边巨佬有那么小声且友好又和平的交流了几句,算上正解,我们搞出了两种解法(DP状态):
- \(dp[i][j][k]\) 为当前是第 \(i\) 个,选出了 \(j\) 个 \(2\),选出了 \(k\) 个 \(5\) 的方案数。
- \(dp[i][j][k]\) 为当前是第 \(i\) 个,选出了 \(j\) 个数,并且有 \(k\) 个 \(5\) 时候选出来 \(2\) 的个数。
我们来分析一下这两种的问题:
按照数据范围来看,我们有 \(200\) 个longlong
,每一个longlong
最大可以存下 \(2^{63}-1\) ,那么分解完质因数就至少有 \(62\) 个 \(2\),那么我们要枚举所有 \(2\) 的情况,当然就是 \(200 \times 62 = 12400\) 种。那么对于 \(5\) 来说,测试之后大概一个longlong
可以存下 \(30\) 个左右,那么就是 \(200 \times 30 = 6000\) 种。
如果使用第一种,我们需要在第二维中跑这 \(12400\) 种情况,又在第三维中跑这 \(6000\) 种情况,显然办不到。
如果使用第二种,我们第三维要选择 \(5\) ,更简单计算,并且数组可以开的下,如果选择有 \(k\) 个 \(2\) 的话就很危险。
下面再来考虑如何转移:
我们考虑枚举的顺序,首先枚举当前是第几个数字,这是 \(i=1 \rightarrow n\) 。之后是当前选择的个数 \(j=1 \rightarrow i\),最后是当前选择 \(5\) 的个数 \(p=6200 \rightarrow five[i]\)。
那么显然,如果选这个数,那么就从选择的个数为 \(j-1\) ,并且选择 \(5\) 的个数 \(p-five[i]\)来转移到当前这个位置。
所以转移方程就是:
\(\boxed{dp[j][p] = max(dp[j][p], dp[j-1][p-five[i]]+two[i])}\)
最后考虑答案:
答案一定是某一个 \(min(p, dp[j][p])\) ,这代表 \(2\) 的个数和 \(5\) 的个数当中取最小的就是末尾 \(0\) 的个数。由于我们的限定条件,我们会发现 \(j \le k\),而且\(1\le p \le 6200\)。所以只需要循环找到答案的最大值就可以。
AC代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define setI(x) freopen(x".in", "r", stdin);
#define setO(x) freopen(x".out", "w", stdout);
#define setIO(x) setI(x) setO(x)
int n, k, ans;
int dp[220][6500];
struct Node {
ll num;
int two, five;
} node[220];
void deal(int x) {
ll now = node[x].num;
while (now % 2 == 0) {
now /= 2;
node[x].two ++ ;
}
while (now % 5 == 0) {
now /= 5;
node[x].five ++ ;
}
}
int main() {
// setIO("dynamic-programming")
scanf("%d%d", &n, &k);
for (int i = 1; i <= n; i ++ ) {
scanf("%lld", &node[i].num);
deal(i);
}
// puts("1");
memset(dp, -0x3f, sizeof dp);
dp[0][0] = 0;
for (int i = 1; i <= n; i ++ ) {
for (int j = i; j >= 1; j -- ) {
for (int p = 6200; p >= node[i].five; p -- ) {
dp[j][p] = max(dp[j][p], dp[j - 1][p - node[i].five] + node[i].two);
}
}
}
for (int i = 1; i <= k; i ++ ) {
for (int j = 6200; j >= 1; j -- ) {
ans = max(ans, min(j, dp[i][j]));
}
}
printf("%d\n", ans);
return 0;
}