A - LCM from 1 to n (欧拉筛+快速幂+二分查找+处理)

Given an integer n, you have to find

lcm(1, 2, 3, ..., n)

lcm means least common multiple. For example lcm(2, 5, 4) = 20, lcm(3, 9) = 9, lcm(6, 8, 12) = 24.

Input

Input starts with an integer T (≤ 10000), denoting the number of test cases.

Each case starts with a line containing an integer n (2 ≤ n ≤ 108).

Output

For each case, print the case number and lcm(1, 2, 3, ..., n). As the result can be very big, print the result modulo 232.

Sample Input

5

10

5

200

15

20

Sample Output

Case 1: 2520

Case 2: 60

Case 3: 2300527488

Case 4: 360360

Case 5: 232792560

题意:给你一个数n求LCM(1, 2,........,n ),LCM 为最大公约数。

思路:该题可以根据n = 10找一下规律。

1    2   3    4    5    6     7     8     9    10

最大公约数为5 * 7 * 8* 9 = 2520,可以看出规律LCM等于p1^x1*p2^x2*p3^x3........pn^xn, p1...pn,为连续的质数。那么xi怎么求,n不断的除以pi,例如上面的例子,8就是10不断的除以2,一共除以3次2,所以2^3 = 8,所以8出来了,9同样的道理,10不断的除以3z最多除以两次3,3^2 = 9,那么9就出来了。所以需要把素数筛出来,但是数据的范围是1e8所以直接开数组不可以,所以需要位图,只需要理解位图的原理,咱么有比较好用的STL的容器bitset和位图的原理一样,可以压缩空间。那么我们直接上欧拉筛,把素数直接筛出来。虽然把素数筛出来但是还是存在问题的,如果你把所有的素数直接遍历到n之间最大的素数,最终的结果指定超时,所以我们就想到,前缀积(就是前缀和的思想),然后查找到比n小的最大的素数,然后在重新遍历以下pri[i]*pri[i] <= n(大于这个条件的素数就只有一个,已经利用前缀积求出来了)的数,求出n连续除以pri[i]的个数减去1,为什么减去1,因为前缀积已经求了,所以需要减去1.

代码:
 


//Full of love and hope for life

#include <iostream>
#include <algorithm>
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <queue>
#include <vector>
#include <bits/stdc++.h>
#define inf 0x3f3f3f3f
//https://paste.ubuntu.com/
//https://www.cnblogs.com/zzrturist/    //博客园
//https://blog.csdn.net/qq_44134712     //csdn

using namespace std;
const int maxn=1e8+5;
typedef long long ll;
const ll mod=1ll<<32;
unsigned int pri[6000000];
unsigned int sum[6000000];
bitset<maxn>vis;//位图压缩空间
int cn=1;

void prime(){//欧拉筛
    pri[0]=0;
    for(int i=2;i<maxn;i++){
        if(vis[i]==false){
            pri[cn++]=i;
        }
        for(int j=1;j<cn&&i*pri[j]<maxn;j++){
            vis[i*pri[j]]=true;
            if(i%pri[j]==0){
                break;
            }
        }
    }
     // pri[cn] = maxn;
    sum[0]=1;
    for(int i=1;i<cn;i++){//前缀积
        sum[i]=sum[i-1]*pri[i];
    }
}

ll qpow(int a,int b){//快速幂
    ll ans=1;
    ll base=a;
    while(b){
        if(b&1){
            ans=(ans*base)%mod;
        }
        base=(base*base)%mod;
        b/=2;
    }
    return ans;
}

int main()
{
    ios::sync_with_stdio(false);
    prime();
    int T,n;
    cin >> T;
    int ca=0;
    while(T--){
         ca++;
         cin >> n;
         int pos=upper_bound(pri+1,pri+cn,n)-pri-1;//二分查找第一个比n大的数,然后减去1,就小于等于n
         ll ans=sum[pos]%mod;
         for(int i=1;i<cn&&pri[i]*pri[i]<=n;i++){//感觉像唯一分解定理
            int t=n;
            int k=0;
            while(t/pri[i]){
                t/=pri[i];
                k++;
            }
            ans=qpow(pri[i],--k)*ans%mod;
         }
         cout << "Case " << ca << ": " << ans%mod << endl;
    }
    return 0;
}

题意:给出n,求1到n的所有数的最小公倍数,模2^32.

思路:看到结果需要模2^32,瞬间想到要尝试用unsigned int来存储结果,它可以表示的数据范围是0~2^32-1,这样不用做任何操作,结果自然就是模2^32的。

接下来就是筛素数了,常用的方法学名是:埃拉托斯特尼筛法,这里有个不错的图来表示(貌似csdn不支持gif了???)

贴一个我以前一直在用的实现:

void Prime ()           //素数打表prime数组从1开始
{
    for (int i=2;i<10005;i++) if (!visit[i])
    {
        prime[++Num_Prime]=i;
        for (int j=i+i;j<NUM;j+=i)
            visit[j]=true;
    }
}

说明:第二重循环可以改成 int j=i*i; 因为对于一个数x,假设它含有质因子i,那么令y=x/i;可以发现,如果所有小于i*i的含有因子i的数字,其y值小于i,在以前的筛选过程中,就会把x筛掉,所以没有必要重新筛选一遍。但是要注意两个int相乘有可能超范围……


这个题貌似用通常的筛法会出现爆内存的情况(我没测),最近学习了一个节省空间的素数筛法,按照目前我的写法visit数组可以减到原来的32分之一。貌似还能优化……

用到了一个叫位图的数据存储方法,理论:

数据结构之位图 | 董的博客

素数判定算法 | 董的博客

可以参考的一个实现:节约空间的筛素数方法 - raomeng1的专栏 - 博客频道 - CSDN.NET

我的实现详见最下面的代码,参考了vjudge上的一份共享的代码。


记录几个常用的数据,摘自http://blog.sina.com.cn/s/blog_484bf71d0100ok5a.html

一百以内有 25 个素数,它们分别是 

2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,89,97。
一千以内有 168 个素数,最后几个是 

907,911,919,929,937,941,947,953,967,971,977,983,991,997。
一万以内有 1229 个素数,最后几个是 

9901,9907,9923,9929,9931,9941,9949,9967,9973。
十万以内有 9592 个素数,最后几个是 

99901,99907,99923,99929,99961,99971,99989,99991。
一百万以内有 78498 个素数,最后几个是 

999907,999917,999931,999953,999959,999961,999979,999983。
一千万以内有 664579 个素数,最后几个是 

9999901,9999907,9999929,9999931,9999937,9999943,9999971,9999973,9999991。
一亿以内有 5761455 个素数,最后几个是 

99999931,99999941,99999959,99999971,99999989。
十亿以内有 50847534 个素数,最后几个是 

999999929,999999937。
一百亿以内有 455052511 个素数,最后几个是 

9999999929,9999999943,9999999967。


下面说一下这个题的思路,直接模拟肯定超时,用记录每个素因子最大个数的方法写了两次都超时……应该是我写的比较挫……有时间再试下。

比较好的思路是把主要的数据先都预处理出来。

以下过程参考了原题的discuss。

定义L(x)为 1, 2, 3, .., x的LCM

则有如下规律:


L(1) = 1

L(x+1) = { L(x) * p    if x+1 is a perfect power of prime p
         { L(x)        otherwise
也就是当x+1是素数p的整数次幂的时候,L(x+1)=L(x)*p;举例如下:

L(2) = 1 * 2
L(3) = 1 * 2 * 3
L(4) = 1 * 2 * 3 * 2      // because 4 = 2^2
L(5) = 1 * 2 * 3 * 2 * 5
L(6) = 1 * 2 * 3 * 2 * 5  // 6 is not a perfect power of a prime
L(7) = 1 * 2 * 3 * 2 * 5 * 7
于是我们可以先把素数连乘的结果预处理出来,然后再对每一个素数的整数次幂根据n的不同进行操作。
 


//Full of love and hope for life

#include <iostream>
#include <algorithm>
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <queue>
#include <vector>
#include <bits/stdc++.h>
#define inf 0x3f3f3f3f
//https://paste.ubuntu.com/
//https://www.cnblogs.com/zzrturist/    //博客园
//https://blog.csdn.net/qq_44134712     //csdn

using namespace std;

const int N=100000007;
int visit[N/32+50];
unsigned int data[5800000];
int prime[5800000],np=0;

void Prime ()   //筛素数,数组从0开始
{
	prime[0]=data[0]=2;
	np=1;
	for (int i=3;i<N;i+=2)   //扫所有奇数
		if (!(visit[i/32] & (1 << ((i/2)%16))))
		{
			prime[np]=i;
			data[np]=data[np-1]*i;  //预处理
			np++;
			for (int j=3*i;j<N;j+=2*i)  //改成i*i会超int范围
				visit[j/32] |= (1 << ((j/2)%16));
		}
}

unsigned int Deal (int n)
{
	int p=upper_bound (prime, prime+np, n)-prime-1;  //定位比n小的第一个素数
	unsigned int ans = data[p];
	for (int i=0; i<np && prime[i]*prime[i] <= n; i++)//此时prime[i]最多10^4
	{//扫所有素数的整数次幂
		int mul = prime[i];
		int tmp = prime[i] * prime[i];
		while (tmp/mul == prime[i] && tmp<=n) //防止int越界
		{
			tmp *= prime[i];
			mul *= prime[i];
		}
		ans *= (mul/prime[i]);
	}
	return ans;
}

int main ()
{
#ifdef ONLINE_JUDGE
#else
	freopen("read.txt","r",stdin);
#endif
	int T,n;
	scanf("%d",&T);
	Prime ();
	for (int Cas=1;Cas<=T;Cas++)
	{
		scanf ("%d",&n);
		printf ("Case %d: %u\n",Cas,Deal(n));
	}
	return 0;
}

/*
6
121
122
243
244
16736299
65302579
Output
Case 1: 2243120960
Case 2: 2243120960
Case 3: 1075942528
Case 4: 1075942528
Case 5: 1048576000
Case 6: 570425344
*/

下面这个是别人的

链接:https://blog.csdn.net/whyorwhnt/article/details/9397289

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ZZ --瑞 hopeACMer

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值