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

题目链接: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) = 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的不同进行操作。

#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
#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
*/


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值