LightOJ 1289 LCM from 1 to n (节省空间的素数筛法+n个数的最小公倍数)

来源:互联网 发布:怪物世界源码 编辑:程序博客网 时间:2024/06/03 15:17

题目链接:http://lightoj.com/volume_showproblem.php?problem=1289

题意:给出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) = 1L(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 * 2L(3) = 1 * 2 * 3L(4) = 1 * 2 * 3 * 2      // because 4 = 2^2L(5) = 1 * 2 * 3 * 2 * 5L(6) = 1 * 2 * 3 * 2 * 5  // 6 is not a perfect power of a primeL(7) = 1 * 2 * 3 * 2 * 5 * 7

于是我们可以先把素数连乘的结果预处理出来,然后再对每一个素数的整数次幂根据n的不同进行操作。

#include <cstdio>#include <algorithm>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#elsefreopen("read.txt","r",stdin);#endifint 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;}/*61211222432441673629965302579OutputCase 1: 2243120960Case 2: 2243120960Case 3: 1075942528Case 4: 1075942528Case 5: 1048576000Case 6: 570425344*/


原创粉丝点击