洛谷P1029 算术基本定理的做法

本文介绍如何运用算术基本定理解决数论中关于最大公约数和最小公倍数的问题,通过欧拉筛法找到素数并分解质因数,最终求得满足特定条件的数对数量。
  • 这道题我是用算术基本定理做的。

算术基本定理:

  • 对于任意一个正整数n,都可将其分解成这样的形式:
    n=p1a1 p2a2 p3a3 …pkak
  • 其中p1,p2,p3…pk指从2开始的素数,即2,3,5,7,11······;a1,a2,a3…ak都是自然数。

思路

  • 求最大公约数和最小公倍数可以用算术基本定理求。
  • gcd*=pow(p[i],min(a1[i],a2[i])); lcm*=pow(p[i],max(a1[i],a2[i]));
  • 那么现在就是要分解质因数了。
  • 要分解质因数首先得把素数都给找出来吧?
//这里我用的是欧拉筛来打素数表
void ols(int n)
{
	for(ri i=2;i<=n;++i)
	{
		if(!flag[i]) prime[cnt++]=i;
		for(ri j=1;j<=cnt;j++)
		{
			if(i*prime[j]>n) break;
			flag[i*prime[j]]=1;
			if(i%prime[j]==0) break;
		}
	}
}
  • 素数都找出来了,那么现在就可以分解质因数了。
for(ri i=1;i<=cnt;++i)//遍历每个素数
{
	while(x%prime[i]==0)//分解x
	{
		a1[i]++;
		x/=prime[i];
	}
	while(y%prime[i]==0)//分解y
	{
		a2[i]++;
		y/=prime[i];
	}
	if(x==1&&y==1) break;//分不了了就退出
}
  • 分完后就能算gcd和lcm啦!
gcd*=pow(prime[i],min(a1[i],a2[i]));
lcm*=pow(prime[i],max(a1[i],a2[i]));
  • 到此,基本工作就做完了。

解题分析

题目传送门

  • 题目要求满足条件的数有几个,那么我们就可以假设满足条件的数为x和y,则有x*y=gcd*lcm,即y=gcd*lcm/x
  • 对于这道题,他给的是两个数的最大公约数和最小公倍数,那么显然满足条件条件的两个数都大于gcd,小于lcm。
  • 在这里很容易想到解的个数为偶数,因为x,y可以交换,所以我们可以假设x<y,最后答案再乘2就行。(注意这里gcd可能等于lcm,那么此时答案就为1,需要特判)
  • 所以我们有gcd<=x<y<=lcm
  • 可以想象满足题意的都是gcd的倍数,所以只需要判断gcd的倍数和此时的y满不满足题意即可。
最后代码如下
#include<cstdio>
#include<cmath>
#include<iostream>
#include<cstring>
#define ri register int
#define maxn 100001
using namespace std;
int flag[maxn]={1,1};//判断是否是素数
int prime[maxn];//素数打表
int a1[maxn];
int a2[maxn];
int cnt=1;//素数表里的素数个数
inline int read()//快读
{
	ri x=0,f=1;
	char ch=getchar();
	while(ch<'0'||ch>'9')
	{
		if(ch=='-') f=-1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9')
	{
		x=x*10-'0'+ch;
		ch=getchar();
	}
	return x*f;
}
void ols(int n)//欧拉筛
{
	for(ri i=2;i<=n;++i)
	{
		if(!flag[i]) prime[cnt++]=i;
		for(ri j=1;j<=cnt;j++)
		{
			if(i*prime[j]>n) break;
			flag[i*prime[j]]=1;
			if(i%prime[j]==0) break;
		}
	}
}
int main()
{
	ri x0=read(),y0=read(),ans=0;
	if(x0==y0)//特判
	{
		printf("1");
		return 0;
	}
	ols(y0);
	for(ri i=1;i*x0<y0;++i)
	{
		ri x=i*x0,y=x0*y0/x;
		if(x>y) break;
		memset(a1,0,sizeof(a1));//注意每次都要清零
		memset(a2,0,sizeof(a2));
		for(ri j=1;j<=cnt;++j)
		{
			while(x%prime[j]==0)//分解x
			{
				a1[j]++;
				x/=prime[j];
			}
			while(y%prime[j]==0)//分解y
			{
				a2[j]++;
				y/=prime[j];
			}
			if(x==1&&y==1) break;
		}
		ri gcd=1,lcm=1;
		for(ri j=1;j<=cnt;++j)
		{
			gcd*=pow(prime[j],min(a1[j],a2[j]));//求x,y的最大公约数
			lcm*=pow(prime[j],max(a1[j],a2[j]));//求x,y的最小公倍数
		}
		if(gcd==x0&&lcm==y0) ans++;//满足条件ans++
	}
	printf("%d",ans*2);//看分析部分
	return 0;
}
  • 看不懂我代码的看这里
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Robin_w2321

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

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

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

打赏作者

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

抵扣说明:

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

余额充值