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 imes 62 = 12400) 种。那么对于 (5) 来说,测试之后大概一个longlong
可以存下 (30) 个左右,那么就是 (200 imes 30 = 6000) 种。
如果使用第一种,我们需要在第二维中跑这 (12400) 种情况,又在第三维中跑这 (6000) 种情况,显然办不到。
如果使用第二种,我们第三维要选择 (5) ,更简单计算,并且数组可以开的下,如果选择有 (k) 个 (2) 的话就很危险。
下面再来考虑如何转移:
我们考虑枚举的顺序,首先枚举当前是第几个数字,这是 (i=1 ightarrow n) 。之后是当前选择的个数 (j=1 ightarrow i),最后是当前选择 (5) 的个数 (p=6200 ightarrow five[i])。
那么显然,如果选这个数,那么就从选择的个数为 (j-1) ,并且选择 (5) 的个数 (p-five[i])来转移到当前这个位置。
所以转移方程就是:
(oxed{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),而且(1le 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
", ans);
return 0;
}