[JSOI2015] 最大公约数

P5502 [JSOI2015] 最大公约数

题目描述

给定一个长度为 \(N\) 的正整数序列 \(A_i\) 。

对于其任意一个连续的子序列 \(A_l,A_{l+1},...,A_r\) ,我们定义其权值 \(W(L,R)\) 为其长度与序列中所有元素的最大公约数的乘积,即 \(W(L,R) = (R-L+1) × \gcd (A_l,...,A_r)\)。

JYY 希望找出权值最大的子序列。

输入格式

输入一行包含一个正整数 \(N\) 。

接下来一行,包含 \(N\) 个正整数,表示序列 \(A_i\) 。

输出格式

输出文件包含一行一个正整数,表示权值最大的子序列的权值。

输入输出样例 #1

输入 #1

5
30 60 20 20 20

输出 #1

80

说明/提示

\(1 \le A_i \le 10^{12}, 1 \le N \le 100000\)


时间复杂度(\(n\log_2^2n\))
重要知识点,一个数列的最大公约数是随着数字数量的增加而变小的,且公约数的数量为\(\log_2 max(A_i)\)级别的。简单证明,假设数列A前m-1个数的最大公约数为x,而第m个的数为\(A_m\),那么前m个数的最大公约数为x和\(A_m\)的最大公约数g。如果\(A_m\)为x的位数,则g==x。否则,g为x的因数,则g小于等于x的一半。如此可知,最多\(\log_2 max(A_i)\)次,公约数就会变为1。
思路:
用RMQ的思想求一个最大公约数的RMQ。
枚举区间的左边界l,而右边界r用倍增的方法枚举,r在一定范围内gcd(A[l…r])的值不变,我们通过倍增枚举找到最大的r。那么用当前的最大公约数乘以(r-l+1)来更新答案,就可以得到最终答案。


#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+10;
int n;
long long f[maxn][20];
int lg[maxn];
long long gcd(long long a,long long b)
{
	return a%b==0?b:gcd(b,a%b);
}
long long ans=0;
long long gcdw;
long long query(int l,int r)
{
	int ch=lg[r-l+1];
	return gcd(f[l][ch],f[r-(1<<ch)+1][ch]);
}
int main()
{
	scanf("%d",&n);
	lg[0]=-1;
	for(int i=1;i<=n;++i)lg[i]=lg[i>>1]+1;
	for(int i=1;i<=n;++i)scanf("%lld",&f[i][0]);
	for(int l=1;l<=lg[n];++l)
	{
		for(int i=1;i+(1<<l)-1<=n;++i)
			f[i][l]=gcd(f[i][l-1],f[i+(1<<(l-1))][l-1]);
	}
	for(int i=1;i<=n;++i)
	{
		int r=i;
		gcdw=f[i][0];
		while(r<=n)
		{
			for(int k=lg[n];k>=0;--k)
				if(r+(1<<k)-1<=n&&query(i,r+(1<<k)-1)==gcdw)r+=(1<<k)-1;	
			ans=max(ans,(r-i+1)*gcdw);
			if(r==n)break;
			if(r<n)gcdw=gcd(gcdw,f[r+1][0]);
		}
	}
	cout<<ans<<endl;
	return 0;
}

posted on 2026-09-29 09:57  gryzy  阅读(6)  评论(0)    收藏  举报

导航