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;
	
}
posted @ 2019-11-12 13:36  筱柒_Littleseven  阅读(167)  评论(0编辑  收藏  举报