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

Least common multiple

 Partychen like to do mathematical problems. One day, when he was doing on a least common multiple(LCM) problem, he suddenly thought of a very interesting question: if given a number of S, and we divided S into some numbers , then what is the largest LCM of these numbers? partychen thought this problems for a long time but with no result, so he turned to you for help!
Since the answer can very big,you should give the answer modulo M.

Input
There are many groups of test case.On each test case only two integers S( 0 < S <= 3000) and M( 2<=M<=10000) as mentioned above.
Output
Output the largest LCM modulo M of given S.
Sample Input

6 23

Sample Output

6


Hint: you can divied 6 as 1+2+3 and the LCM(1,2,3)=6 is the largest so we output 6%23=6.
题意:

将一个数拆分成几个数的和,并求出这几个数最小公倍数的最大值,结果模m

分析:

首先我们根据题目意思可以知道,为了让最小公倍数最大,我们需要让分开的每个数都是互质的,这样答案直接就是分成的每个数相乘了。所以我们可以先筛选出1~3000的素数,然后从这些素数里面取或者多个组成n,最后求n填满时最大的LCM,所以是完全背包
dp[i]表示将数i分成若干个数可以得到的最大的最小公倍数

互质存在以下情况:

1) 1和任何自然数互质
2) 两个不同的质数互质
3) 一个质数和一个合数,当他们不是倍数关系时互质
4) 两个合数,当他们不含有相同的质因子时互质

根据算数基本定理我们只需要考虑选择素数或者素数的幂

由于lcm结果过大会溢出,题目要求的结果有取模,但如果在递推dp的过程中取模,在比较选择最优结果时就会出错,例如模数为m,上一次求出dp[k]=m+1,结果取模得1,下一次随便一个大于1得答案就会覆盖掉正确答案。但是不取模就会溢出,肯定也会造成答案错误。这里使用了取log来保存答案

那么对于我们枚举的一个素数p,当前的最小公倍数应该为

dp[j]=dp[jp]p d p [ j ] = d p [ j − p ] ∗ p (因为互质所以直接用j-p这个数可以得到的最大的最小公倍数乘p)

因为会溢出所以取log后实际得到

log(dp[j])=log(dp[jp])+log(p) l o g ( d p [ j ] ) = l o g ( d p [ j − p ] ) + l o g ( p )

而对于一般的情况p的幂次

我们得到了

log(dp[j])=log(dp[jp])+log(pk)=log(dp[jp])+klog(p) l o g ( d p [ j ] ) = l o g ( d p [ j − p ] ) + l o g ( p k ) = l o g ( d p [ j − p ] ) + k ⋅ l o g ( p )

因此我们直接让dp改使用double类型

就得到递推式

dp[j]=dp[jp]+klog(p) d p [ j ] = d p [ j − p ] + k ⋅ l o g ( p )

通过dp比较大小,通过ans数组记录实际的取模值

code:

#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
#include <cmath>
using namespace std;
int n,m;
int prime[1000];
bool isprime[3005];
double dp[3005];
int ans[3005];
int num;
void init(){
    num = 0;
    memset(isprime,true,sizeof(isprime));
    isprime[0] = isprime[1] = false;
    for(int i = 2; i <= 3000; i++){
        if(isprime[i]){
            prime[num++] = i;
            for(int j = i + i; j <= 3000; j += i){
                isprime[j] = false;
            }
        }
    }
}
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 < num && prime[i] <= n; i++){
            double cnt = log(prime[i]*1.0);
            for(int j = n; j >= prime[i]; j--){
                for(int k = 1,p = prime[i]; p <= j; p *= prime[i],k++){
                    if(dp[j-p] + cnt * k > dp[j]){
                        dp[j] = dp[j-p] + cnt * k;
                        ans[j] = ans[j-p] * p % m;
                    }
                }
            }
        }
        printf("%d\n",ans[n]);
    }
    return 0;
}
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值