LCM from 1 to n(素数筛+位图优化空间)

题目链接:LCM from 1 to n

题意:求1 - n的lcm

思路:

  1. 最小公倍数的适用范围:分数的加减法,中国剩余定理(正确的题在最小公倍数内有解,有唯一的解)。因为,素数是不能被1和自身数以外的其它数整除的数;素数X的N次方,是只能被X的N及以下次方,1和自身数整除。所以,给最小公倍数下一个定义:S个数的最小公倍数,为这S个数中所含素因子的最高次方之间的乘积
    如:求756,4400,19845,9000的最小公倍数
    因756=2 * 2 * 3 * 3 * 3 * 7,4400=2 * 2 * 2 * 2 * 5 * 5 * 11,19845=3 * 3 * 3 * 3 * 5 * 7 * 7,9000=2 * 2 * 2 * 3 * 3 * 5 * 5 * 5,这里有素数2,3,5,7,11.2最高为4次方16,3最高为4次方81,5最高为3次方125,7最高为2次方49,还有素数11。得最小公倍数为16 * 81 * 125 * 49 * 11=87318000.2,自然数1至50的最小公倍数,因为,√50≈7,所以,在50之内的数只有≤7的素数涉及N次方。在50之内,2的最高次方的数为32,3的最高次方的数为27,5的最高次方的数为25,7的最高次方的数为49,其余为50之内的素数。所以,1,2,3,4,5,6,…,50的最小公倍数为:32 * 27 * 25 * 49 * 11 * 13 * 17 * 19 * 23 * 29 * 31 * 37 *4 1 *4 3 * 47=3099044504245996706400(摘自百度百科)
  2. 那么求 1 - n 的LCM就是求 <= n 的所有素数的最高次幂的乘积
    如 LCM( 1 - 5 ) = 22 * 31 * 51
  3. 那么首先要筛素,直接开1e8的数组会超内存所以我们进行位图优化。
  4. 然后我们预处理出小于 n 的所有素数的乘积,
  5. 枚举所有小于 n 的素数,求他们的最高次幂

位图:
位图是一种优化空间的方法,我们知道在计算机中一个 int 是 32 个字节,那么,我们不妨用每一个字节表示是否存在一个数,那么一个 int 的位置就可以表示 32 个数。
比如vis[0] :0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0表示5存在

#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<algorithm>
#include<queue>
#include<stack>
#include<vector>
#include<map>
#include<set>
#define lowbit(x) x & (-x)
#define inf 0x3f3f3f3f
using namespace std;

typedef long long ll;

const int N = 1e8+10;
const ll mod = (ll)1 << 32;

int isp[N/32+50];
unsigned int mul[6000000];
int p[6000000], totp;

bool Get(int x)
{
    int a = x / 32, b = x % 32;
    return 1 << b & isp[a];
}

void Set(int x)
{
    int a = x / 32, b = x % 32;
    isp[a] |= (1 << b);
}

void getpri()
{
    Set(0);
    Set(1);
    for(int i = 2; i < 1e8; i++)
    {
        if(!Get(i))
        {
            p[totp++] = i;
            if(i > N / i) continue;
            for(int j = i * i; j < 1e8; j += i) Set(j);
        }
    }
    mul[0] = p[0];
    for(int i = 1; i < totp; i++) mul[i] = (ll)mul[i-1] * p[i] % mod;//求素数前缀积
}

unsigned int getans(int n)
{
    unsigned int ret = 0;
    int last = upper_bound(p , p + totp, n) - p - 1;//第一个大于 n 的位置减一就是最接近
    ret = mul[last];                                //n 的素数,然后直接用前缀积
    for(int i = 0; i < totp && p[i] * p[i] <= n; i++)
    {
        int now = p[i];
        int tmp = p[i] * p[i];
        while(tmp / now == p[i] && tmp <= n)//两个判断条件一个都不能少, 前面条件的是为
        {                                   //了防止超爆 int
            tmp *= p[i];
            now *= p[i];
        }
        ret *= (now / p[i]);
    }
    return ret;
}

int main()
{
    getpri();
    int t, n;
    scanf("%d", &t);
    for(int ca = 1; ca <= t; ca++)
    {
        scanf("%d", &n);
        printf("Case %d: %u\n", ca, getans(n));
    }
    return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值