Problem E - Mishka and Divisors
题目大意
给n,k,给n个数,让你挑出m个数,使得m个数的乘积是k的倍数,输出最小的m。
如果有多个方案,输出sum最小的方案,(方案输出所选数的下标)
数据范围:n <= 1000, k <= 10^12, ai<=10^12。
解题分析
dp[i][j]表示前i个数里选一些数,使得乘积是j的倍数的最小的m。
sum[i][j]表示最优方案对应的sum
可以得知,dp[i][j]=min(dp[i-1][j],dp[i-1] [ j/gcd(j,a[i])] )
也就是要么不选a[i],如果选a[i]就在前面选前i-1个数取到j/gcd(j,a[i])的方案
记得相应的sum值也要更新。
这里的i就是1到n,而j应该是 k的约数们,可以知道k的约数个数大概是lg至上,sqrt未满的一个数量级,
粗略估计一下不超过1W ? 不过最后内存用了17X mb ...还好是cf
把k的约数们用map映射一下就好了
时间卡得紧,会T,注意到 求gcd(j,a[i])的时候,可以先令b[i]=gcd(k,a[i]),最后直接求gcd(j,b[i])能省一些时间
参考程序
#include <cstdio>
#include <cmath>
#include <cstring>
#include <string>
#include <algorithm>
#include <queue>
#include <map>
#include <set>
#include <vector>
#include <iostream>
using namespace std;
typedef long long ll;
const int N = 1005;
ll aa[N];
ll bb[N];
ll prim[11000+50];
ll ok=0;
void f(ll k)
{
for (ll i=1; i*i<=k; i++)
if (k%i==0)
{
prim[++ok]=i;
if (k/i!=i)
prim[++ok]=k/i;
}
}
ll dp[N][11000+50];
ll sum[N][11000+5];
map <ll,int>idx;
ll gcd(ll a,ll b)
{
if (!b) return a;
else return gcd(b,a%b);
}
int main()
{
ll n,k;
cin>>n>>k;
ll tmp=k;
for (int i=1; i<=n; i++)
{
scanf("%lld",&aa[i]);
bb[i]=gcd(k,aa[i]);
}
if (k==1)
{
printf("1\n");
printf("%d\n",(min_element(aa+1,aa+1+n)-aa));
return 0;
}
f(k);
sort(prim+1,prim+1+ok);
for (int i=1; i<=ok; i++)
idx[prim[i]]=i;
for (int j=2; j<=ok; j++)
{
if (aa[1]%prim[j]==0)
dp[1][j]=1,sum[1][j]=aa[1];
else dp[1][j]=n+1;
}
dp[1][1]=0;
for (int i=2; i<=n; i++)
{
for (int j=1; j<=ok; j++)
{
dp[i][j]=dp[i-1][j];
sum[i][j]=sum[i-1][j];
ll v=prim[j]/gcd(prim[j],bb[i]);
int id=idx[v];
if (dp[i-1][id]+1 < dp[i][j] )
{
dp[i][j]=dp[i-1][id]+1;
sum[i][j]=sum[i-1][id]+aa[i];
}
else if (dp[i-1][id]+1==dp[i][j])
{
if (sum[i][j]>sum[i-1][id]+aa[i])
sum[i][j]=sum[i-1][id]+aa[i];
}
}
}
if (dp[n][ok]>n)
{
printf("-1\n");
return 0;
}
printf("%lld\n",dp[n][ok]);
tmp=k;
for (int i=n; i>=1; i--)
{
int id=idx[tmp];
if (dp[i][id]==dp[i-1][id]&& sum[i][id]==sum[i-1][id]) continue;
printf("%d ",i);
tmp/=gcd(tmp,bb[i]);
}
printf("\n");
return 0;
}