Codeforces 703E DP + 因数分解 +离散化

题意:给你n个数,和一个数m, 问最小需要多少个数,可以让这些数乘起来是m的倍数。如果有多组,取和最小的那一组。

思路:因为m的范围到1e12,并且和取模相关,所以容易想到处理出m的约数,然后离散化一下,降低DP的第二维的复杂度,因为如果这些数的乘积不是m的约数,就没有意义了。dp[i][j]表示处理到第i个数,约数是j的最小个数。dp需要存pair,因为要求个数一样的时候和最小。可以提前把a和m求gcd以降低复杂度,注意特判m为1的情况。

代码:

#include <bits/stdc++.h>
#define LL long long
#define pii pair<LL, LL>
#define INF 1e16
using namespace std;
const int maxn = 1010;
pii dp[maxn][7010];
map<LL, int> mp;
LL f[7010], tot;
int n;
LL m, a[maxn], b[maxn]; 
void init(LL x) {
	for (LL i = 1; i * i <= x; i++) {
		if(x % i == 0) {
			f[++tot] = i;
			if(i * i != x)
				f[++tot] = x / i;
		}
	}
	sort(f + 1, f + 1 + tot);
	for (int i = 1; i <= tot; i++)
		mp[f[i]] = i;
}
int main() {
	scanf("%d%lld", &n, &m);
	init(m);
	for (int i = 1; i <= n; i++) {
		scanf("%lld", &a[i]);
		b[i] = __gcd(a[i], m);
	}
	if(m == 1) {
		LL ans = 1e15, pos = 0;
		for (int i = 1; i <= n; i++) {
			if(a[i] < ans) {
				ans = a[i];
				pos = i;
			}
		}
		printf("%d\n%lld\n", 1, pos);
		return 0;
	}
	for (int i = 2; i <= tot; i++)
		dp[0][i] = pii(INF, 0);
	for (int i = 1; i <= n; i++) {
		for (int j = 1; j <= tot; j++) {
			dp[i][j] = dp[i - 1][j];
			int pos = mp[f[j] / __gcd(f[j], b[i])];
			pii tmp = pii(dp[i - 1][pos].first + 1, dp[i - 1][pos].second + a[i]);
			dp[i][j] = min(dp[i][j], tmp);
		}
	}
	if(dp[n][tot].first > n) printf("-1\n");
	else {
		printf("%lld\n", dp[n][tot].first);
		LL now = f[tot], pos = tot;
		for (int i = n; i >= 1; i--) {
			if(dp[i][pos] != dp[i - 1][pos]) {
				printf("%d ", i);
				now /= __gcd(b[i], now);
				pos = mp[now];
			}
		}
	}
}

  

猜你喜欢

转载自www.cnblogs.com/pkgunboat/p/10755688.html