数论之最大公约数

前言

总结一下最大公约数出题的几种方向和性质定理
当然一般的简单模拟题,枚举题就不分类提出了

方向总结

1.gcd(a,b) ∗ * lcm(a,b)==a ∗ * b

例题1

给出了a,b的最大公约数和最小公倍数,则积知道了,通过积枚举a和b的值,用gcd判定一下即可。

#include<iostream>
using namespace std;

int gcd(int a,int b) {
    
    
	if(b==0)return a;
	else return gcd(b,a%b);
}

int main(){
    
    
	int x0,y0,x,ans=0;
	cin>>x0>>y0;
	int t=x0*y0;
	for(int i=x0;i<=y0;i+=x0){
    
    
		if(t%i!=0)continue;
		if(gcd(i,t/i)==x0)ans++;
	}
	cout<<ans;
	return 0;
}

例题2

求a,b,c三个数的最小公倍数
求出a和b的最小公倍数x和b和c的最小公倍数y
则a,b,c的最小公倍数为x和y的最小公倍数

#include<iostream>
using namespace std;

long long gcd(long long a,long long b){
    
    
	if(b==0)return a;
	else return gcd(b,a%b);
}
int main() {
    
    
	long long a,b,c;
	cin>>a>>b>>c;
	long long x=a*b/gcd(a,b);
	long long y=b*c/gcd(b,c);
	cout<<x/gcd(x,y)*y;
	return 0;
}

2.求所有数的gcd(重要)

惯用思维1:
所有数的gcd等于gcd(a1,gcd(a2,gcd(a3,gcd(a4,an))))
即循环一遍求gcd,但这有个很大的局限,就是这些数不能变化

思维2:
所有数的gcd等于所有数共有的(素因子)(该素因子的共有数量)之积
∏ p i a i \prod{pi^{ai}} piai,pi为素因子,ai为所有数拥有pi素因子的最少数

例如:12 18 6
12的素因子22,31
18的素因子21,32
24的素因子23,31
2这个素因子拥有的最少数是1,3这个素因子拥有的最少数是1
所以最大公约数是2 ∗ * 3 = 6

第二种思维有个很大的好处,就是当我们需要对某个数进行乘除处理,只需要对乘除的数进行质因数分解即可。

思维3:
所有数的gcd等于所有数共有的最大因子,即最大因子出现n次

例题:

例题1

问题描述:
要从n个数中找 i 个数,使得这 i 个数的gcd最大,i 从1到n。

问题分析:
运用思维3,我们预处理出每个数作为因子在所有数中出现的次数。则取 i 个数的最大因子就是出现次数超过 i 次的最大的因子。
看上去要两层循环?但是我们可以看出,i 从1-n循环遍历,其对应的最大因子是从大到小单调递减的。

代码如下:

#include<iostream>
using namespace std;

int num[1000005];

int add(int x) {
    
    
	for(int i=1; i*i<=x; i++) {
    
    
		if(x%i==0) {
    
    
			num[i]++;
			if(i*i!=x)num[x/i]++;
		}
	}
}
int main() {
    
    
	int n,x;
	cin>>n;
	for(int i=1; i<=n; i++) {
    
    
		cin>>x;
		add(x);
	}
	int ans=1e6;
	for(int i=1;i<=n;i++){
    
    
		for(int j=ans;j>=1;j--){
    
    
			if(num[j]>=i){
    
    
				ans=j;
				break;
			}
		}
		cout<<ans<<endl;
	}
	return 0;
}

例题2(difficult,建议观看)

运用到思维2

问题分析:

假设n个数都只有质因子2,则所有数的最大公约数取决于所有数拥有质因子数的最小数

例如:
{4,8,32,1024}分别拥有{2,3,5,10}个,则最大公约数为ans=2^2=4

当第一个数乘于4变成了16,则第一个数多了两个2
也就是分别拥有{4,3,6,8},则最大公约数变成了2^3=8

从22->23的变化,其实是{2,3,5,10}到{4,3,6,8}两个集合最小值的变化
最小值从2变成了3,则ans多乘了一个2

我们用mutiset维护这个集合,从原来的最小值到变化后的最小值差多少,就乘多少个2
比如上面变化的2->4,就是 erase(2),再 insert(4),然后集合最小值从2变成了3,相差1,ans就多乘了一个2

我们怎么知道是要修改2->4?而不是修改3->5?
我们需要用一个数组记录第i个数有多少个2,当乘上2add
第i个数多了add个2
则就是删除(原来第i个数原来2的数量),
再插入(原来第i个数原来2的数量+add)
此时第i个数2的数量同样要修改即加上add

我们来实现一下上述过程

现在我们把2换成div,当第i个数乘上divadd
mutiset cnt;维护集合
int divisor[N];存储第i个数拥有的div的数量
代码如下:

int t=divisor[i];//原来拥有div的数量
divisor[i]+=add;//现在拥有div的数量
last=*(cnt.begin);//原来的集合的最小值
cnt.erase(t);//删除原来
cnt.insert(t+add);//插入现在
for(int j=last+1;j<=*(cnt.begin();j++)ans=ans*div%mod;//循环集合最小值到现在的集合最小值

继续分析
我们对新乘上的数进行质因子分解,保证div一定是质因子,但质因子不一定是2,我们需要维护每个质因子的集合,存储每个数拥有的每个质因子的数量。
则需要
mutiset cnt[max_value]
int cnt_divisor[N][max_value]
数组不能太这么大!!!换map
mutiset cnt[max_value]
map cnt_divisor[N]

接下来我们需要对新乘上的数做质因子分解,然后把每个质因子都用上述操作处理一遍。

时间复杂度:O((n+q)(sqrt(x)+logx)),TLE

还是不行!!!
质因子分解其实可以进行一次预处理,这样子对每个数进行质因子分解时只需要 l o g n logn logn即可,总时间复杂度变为O((n+q) l o g x logx logx+xlogx),

怎么质因子分解???
用next数组记录每个数的最小质因子
然后循环除以质因子即可

void init() {
    
    
	for(int i=2; i<=N; i++) {
    
    
		if(next[i]!=0)continue;
		for(int j=i*i; j<=N; j+=i) {
    
    
			next[j]=i;
		}
	}
}
void add(int x) {
    
    
	while(x!=1) {
    
    
		int add=0,div=next[x];
		while(next[x]==div) {
    
    
			add++;
			x=x/div;
		}
	}
}

完整代码如下:

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<map>
#include<set>
using namespace std;

const int N=2e5;
const int mod=1e9+7;

int nxt[N+10];
long long ans=1,n;
multiset<int>cnt[N+10];
map<int,int>cnt_divisor[N+10];

void init() {
    
    //预处理质因数分解 
	for(long long i=2; i<=N; i++) {
    
    
		if(nxt[i]!=0)continue;
		if(i*i>N)break;
		nxt[i]=i;
		for(long long j=i*i; j<=N; j+=i) {
    
    
			if(nxt[j]==0)nxt[j]=i;
		}
	}
}
void add(int i,int x) {
    
    
	while(x!=1) {
    
    
		int add=0,div=nxt[x];
		while(nxt[x]==div) {
    
    
			add++;
			x=x/div;
		}//处理得到x对应的质因数分解的某个div^add

		int t=cnt_divisor[i][div];//原来拥有的div个数
		cnt_divisor[i][div]+=add;//现在拥有的div个数

		int last=0;
		if(cnt[div].size()==n)last=*(cnt[div].begin());//原来拥有div个数的最小值

		if(t!=0)cnt[div].erase(cnt[div].find(t));//删除原来
		cnt[div].insert(t+add);//添加现在

		if(cnt[div].size()==n) {
    
    
			int now=*(cnt[div].begin());//现在拥有div个数的最小值
			for(int j=last+1; j<=now; j++)ans=ans*div%mod; //计算
		}
	}
}

int main() {
    
    
	init();

	int q,k,x;
	cin>>n>>q;
	for(int i=1; i<=n; i++) {
    
    
		scanf("%d",&x);
		add(i,x);
	}
	for(int i=1; i<=q; i++) {
    
    
		scanf("%d%d",&k,&x);
		add(k,x);
		printf("%d\n",ans);
	}
	return 0;
}

3.求所有数的lcm(先不看这个)

4.关于lcm的枚举题

对于枚举ax+by的最优值在已知a和b情况下的x和y
若a较大,则b的枚举量y不会超过lcm(a,b)/b

例题1
问题分析:
设b/a<d/c,即bc<ad,此时相当于买第一种比买第二种更便宜。

假设买了ac束花
买第一种花的花费为:cb
买第二种花的花费为:ad
而bc<ad
即:如果买了a朵以上的第二种花,其中ac束不如来买c朵第一种花
即:第二种花买的数量不超过a朵

其实,当我们买了lcm(a,c)束花的时候也有上述的讨论
即:第二种花买的数量不超过lcm(a,c)/c朵
只是用ac更好理解

5.裴蜀定理

裴蜀定理是这样描述的:
已知a,b,
则有:ax+by=c,且gcd(a,b)|c,(x,y为任意整数)
即由a和b任意组合的c一定是a与b最大公约数的倍数
ps:在扩展欧几里得里也有和这很像的式子
拓展成多个数也成立
即:a1x1+a2x2+a3x3+……+anxn=c,gcd(a1,a2,a3,……,an)|c

模板题链接
问题分析:求n个数任意组合成的最小正数c,由裴蜀定理知c一定是所有数gcd的倍数,最小c自然是一倍的gcd。
ps:求gcd时要把负数变成正数
代码略

6.思维题汇总

例题1
问题描述:从1-n中选出k个数,求这k个数的最大公约数最大为多少
问题分析:
假设最大为x,易知:有x这个因子的数分别为x,2x,3x,4x,kx
且kx<=n,要求最大的x且保证有k个这样的因子,则x=n/k
代码略

例题2
问题分析:
当k为质数,
若2k<n,则1-n 所有数都与k互质,一次即可
若2k>=n,则第一次1-n中除了k的倍数,其他都被告知了;由于所有的对于所有k的倍数kx,其对应的kx-1都与其互质,所以第二次所有数都会被告知。

当k为合数,
由题目提示的定理可知,存在一个质数p属于[n/2,n],且k不可能为p的倍数,kp互质,第一次p会被告知;接下来符合上述的情况1,所有数都与p互质,再来一次即可

代码略

猜你喜欢

转载自blog.csdn.net/weixin_43602607/article/details/114534388