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]; } } } }