HDU 3092 Least common multiple

2 篇文章 0 订阅

题目链接:Least common multiple

 

题意:给你两个数n和m,让你将n切割成多个数字,求如何切割使得得到的数字的最小公倍数最大,最后输出最大公倍数对m取模后的结果。

 

题解:

首先,最小公倍数的求解:lcm(a,b)=a*b/gcd(a,b)   所以,要使最小公倍数lcm最大,则必须要让最大公约数gcd最小。而当两个数互质时,其最大公约数为1,此时满足题目所需要求。所以可以将题目转换成将n分割成若干个互质的数,并使这若干个质数乘积最大。

其次,互质的情况有一下几种:(1)1与任何自然数互质;(2)两个不同的质数互质;(3)一个合数与一个质数,当这两个数不是倍数关系时互质;(4)不含相同质因数的两个合数互质。

然后,根据上面的条件,知道现在需要知道小于等于n的质数有哪些,由于题目中n<=3000,不算太大,所以可以用素数筛将小于等于3000的素数存到一个数组中,然后在数组中找满足条件的素数进行后续操作(我用的是优化后的线性素数筛),且选择的素数可以是同一个素数选取多次,与完全背包类似。

其中,n相当于背包的总容量,而小于等于n的素数相当于待选的物品,每个物品的重量和价值就是这个数本身的大小,递推公式为:dp[i]=max(dp[i],dp[i-p]*p)  (其中p为选择的素数大小,dp数组存的是i分解之后的数的最小公倍数),但是由于lcm的结果过大会溢出,同时,题目中还要求将结果对m取模,但如果在递推dp的过程中取模,在比较选择最优结果时就会出错,例如模数为m,上一次求出dp[k]=m+1,结果取模得1,下一次随便一个大于1得答案就会覆盖掉正确答案。但是不取模就会溢出,肯定也会造成答案错误。所以参照已有的题解,需要取对数来存储答案和比较最优结果,然后再新建一个新数组ans来存储取模后的结果。而dp数组就变成double类型,同时递推公式变为:dp[i]=max(dp[i],dp[i-p]+q*p)   (其中,q为素数p为选取的次数,然后在取对数后乘积变成加法,即原本是dp[j-p]*(p^q),取对数后变成dp[i-p]+q*p),而存结果的数组:ans[i]=max(ans[i],(ans[i-p]*p)%m)   

AC代码:

//HDU 3092
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <cmath>
#include <cstring>
using namespace std;
int N,M,prime[3010],cnt=0;//cnt标记1...3000的素数个数;prime[]存素数
bool isprime[3010];//标记是否为素数的数组
double dp[3010];//取对数之后的结果
int ans[3010];//存求模后的结果
void init()//优化后的线性素数筛,查找并存储3000以内的素数
{
    memset(isprime,1,sizeof(isprime));//初始默认所有的数都是素数
    isprime[0]=isprime[1]=0;//0和1不是素数
    for(int i=2;i<=3000;i++)//对查找2...3000之间的素数
    {
        if(isprime[i])
            prime[cnt++]=i;//保存素数
        for(int j=0;j<cnt&&prime[j]*i<=3000;j++)//j为素数数组中的下标,遍历0...cnt;i为素数要乘的倍数
            isprime[prime[j]*i]=0;//筛掉小于等于i的素数和由i的乘积构成的素数
    }
}
int main()
{
    init();//得到素数数组
    while(~scanf("%d%d",&N,&M))
    {
        memset(dp,0,sizeof(dp));//数组初始化
        for(int i=0;i<=N;i++)
            ans[i]=1;//数组初始化
        for(int i=0;i<cnt&&prime[i]<=N;i++)//在素数数组中开始遍历查找,且不能超过N
        {
            double tmp=log(prime[i]*1.0);//log的参数是double类型,暂存素数取对数后的结果值
            for(int j=N;j>=prime[i];j--)//因为要找最大的最小公倍数,所以质数从大的开始找
                for(int q=1,p=prime[i];p<=j;p*=prime[i],q++)//q记录素数prime[i]被选取的次数,即p中prime[i]的幂的次数,便于对dp数组更新的操作
                    if(dp[j]<dp[j-p]+q*tmp)
                    {
                        dp[j]=dp[j-p]+q*tmp;
                        ans[j]=(ans[j-p]*p)%M;
                    }
        }
        printf("%d\n",ans[N]);
    }
    return 0;
}

 

参考:

Least common multiple HDU - 3092题解

hdu 3092 Least common multiple(完全背包+数论)

ACM基础知识储备-快速筛法求素数

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值