题意:给定一个最多个数的序列,从中选出最少个数的数字,使得他们的乘积是k的倍数,若有多种选择方式,输出选出数字和最小的一种,若有多种,输出任意一种。
题解:
动态规划,dp[i][j]表示从前i个数里选,所得乘积是j的倍数。显然dp[i][j]=max(dp[i-1][j],dp[i-1][j/gcd(j,a[i])])。由于k可能很大,所以只需令j分别等于k的每个约数即可。
确定k的约数的时候令i从1到sqrt(k)循环判断,注意由于k很大,这里的i要使用longlong。
#include<bits/stdc++.h>
using namespace std;
#define ft first
#define sd second
#define mp make_pair
long long a[1100], k, f[11000], b[1100];
int fc, n;
pair<int, long long> dp[1100][11000];
map<long long , int> e;
int main()
{
//freopen("input.txt", "r", stdin);
scanf("%d%I64d", &n, &k);
long long t = k;
for (int i = 1; i <= n; i++)
{
scanf("%I64d", &a[i]);
b[i] = __gcd(k, a[i]);
t /= __gcd(t, a[i]);
}
if (t != 1)
{
printf("-1\n");
return 0;
}
if (k == 1)
{
printf("1\n%d\n", (int)(min_element(a + 1, a + n + 1) - a));
return 0;
}
e.clear();
fc = 0;
for (long long i = 1; i * i <= k; i++)
{
if (k % i != 0) continue;
f[fc++] = i;
if (i * i != k) f[fc++] = k / i;
}
sort(f, f + fc);
for (int i = 0; i < fc; i++)
e[f[i]] = i;
for (int i = 1; i < fc; i++)
dp[0][i] = mp(n + 1, 0);
dp[0][0] = mp(0, 0);
for (int i = 1; i <= n; i++)
for (int j = 0; j < fc; j++)
{
dp[i][j] = dp[i - 1][j];
long long v = e[f[j] / __gcd(f[j], b[i])];
if (dp[i][j] > mp(dp[i - 1][v].ft + 1, dp[i - 1][v].sd + a[i]))
dp[i][j] = mp(dp[i - 1][v].ft + 1, dp[i - 1][v].sd + a[i]);
}
printf("%d\n", dp[n][fc - 1].ft);
t = k;
for (int i = n; i > 0; i--)
{
if (dp[i][e[t]] == dp[i - 1][e[t]]) continue;
printf("%d ", i);
t /= __gcd(t, b[i]);
}
printf("\n");
//fclose(stdin);
return 0;
}