CF837D Round Subset

题目简述

我们把一个数的 roundness 值定义为它末尾 0 的个数。给你一个长度为 $n$ 的数列,要求你从中选出 $k$ 个数,使得这些选出的数的积的 roundness 值最大。$n,k≤200,\ 数列中的每一项≤10^{18}$

思路

容易想到把每个数中因子 2 和因子 5 提取出,求出它们的数量 —— 因为原题相当于只要求因子 10 的数量,其他的因子对答案没有贡献。

一开始的思路是设 $f_{i,j}$ 表示前 $i$ 个数选了 $j$ 个的答案,但是注意到这个状态难以转移 —— 不能简单的将一个数中 2 的数量和 5 的数量去最小值作为贡献,因为这个数中的 5 可以别的数中的 2 产生贡献(反过来也是一样)。

可以将原问题转化为二维费用背包问题 —— 将「选出数的 5 的总数」作为一维费用(其实 2 也是可以的,但是一般来讲 5 的话状态不是会少一些嘛),设 $f_{i,j,k}$ 表示前 $i$ 个数选了 $j$ 个,选出的数 5 的总数是 $k$ 的情况下最多的 2 的个数。最后更新答案时使用 min(f[n][k][i], i) 更新。注意第一维可以压掉。

Code

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<climits>
#define int long long
using namespace std;
const int MaxN = 202, MaxLog = 20004;
int n, k, two[MaxN], five[MaxN], f[MaxN][MaxLog], ans;

inline int Read(){
	int num = 0, op = 1;
	char ch = getchar();
	while(!isdigit(ch)){
		if(ch == '-') op = -1;
		ch = getchar();
	}
	while(isdigit(ch)){
		num = num * 10 + ch - '0';
		ch = getchar();
	}
	return num * op;
}

inline void Divide(int cnt, int x){
	while(x % 2 == 0){
		two[cnt]++;
		x /= 2;
	}
	while(x % 5 == 0){
		five[cnt]++;
		x /= 5;
	}
	return;
}

signed main(){
	n = Read(), k = Read();
	for(int i=1; i<=n; i++){
		int a = Read();
		Divide(i, a);
	}
	memset(f, -0x3f3f3f, sizeof(f));
	f[0][0] = 0;
	int sum = 0;
	for(int l=1; l<=n; l++){
		sum += five[l];
		for(int i=min(k,l); i>=1; i--){
			for(int j = sum; j>=five[l]; j--)
				f[i][j] = max(f[i][j], f[i-1][j-five[l]] + two[l]);
		}
	}
	for(int i=1; i<=min(k,n); i++)
		for(int j=0; j<=sum; j++)
			ans = max(ans, min(j, f[i][j]));
	printf("%lld", ans);
	return 0;
}
Sorry potatoler, but I cannot recognize you.
Built with Hugo
Theme Stack designed by Jimmy