cf/codeforces #365 E - Mishka and Divisors 数学+背包dp+gcd

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值