P5502 [JSOI2015]最大公约数(结论,ST表)

该博客讨论了一种优化算法,用于解决寻找序列中连续子序列的最大公约数与序列长度乘积最大值的问题。利用最大公约数的性质,通过构建ST表实现快速查询,并采用二分查找更新答案,实现了O(nlog^2n)的时间复杂度解法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目链接:点击这里

题目大意:
给定一个长度为 n n n 的序列 a i a_i ai ,你需要找到这个数组的一个子序列(要求编号连续),使得该序列中所有数的最大公约数和序列长度的乘积最大,并输出这个最大值。
即求: m a x l ≤ r ( r − l + 1 ) ⋅ gcd ⁡ ( a l , a l + 1 , . . . , a r ) max_{l\le r}(r-l+1)·\gcd(a_l,a_{l+1},...,a_r) maxlr(rl+1)gcd(al,al+1,...,ar)

题目分析:
一个经典结论:长度 n n n 的序列的所有子序列的最大公约数,最多有 log ⁡ n \log n logn 个,即集合 { gcd ⁡ ( a l , a l + 1 , . . . , a r ) ∣ l ≤ r } \{\gcd(a_l,a_{l+1},...,a_r)|l\le r\} {gcd(al,al+1,...,ar)lr} 的元素个数不超过 l o g 2 n log_2{n} log2n
证明:
gcd ⁡ ( i , n ) ≤ n / 2   ( i < n ) \gcd(i,n)\le n/2\ (i<n) gcd(i,n)n/2 (i<n) ,因为小于 n n n 且能被 n n n 整除的最大数字一定小于等于 n / 2 n/2 n/2
所以子序列的 gcd ⁡ \gcd gcd 往两边扩展的时候要么不变,要么减半,所以最多产生 log ⁡ n \log n logn 个新元素,即集合大小为 O ( log ⁡ n ) O(\log n) O(logn)

由结论知,在我们固定左端点为 i i i 时, [ i , n ] [i,n] [i,n] 这个去加子序列的 gcd ⁡ \gcd gcd 最多有 O ( log ⁡ n ) O(\log n) O(logn) 个,所以我们不妨把区间 gcd ⁡ \gcd gcd 挂到 s t st st 表上,然后就可以快速查询区间 gcd ⁡ \gcd gcd 了,然后二分 gcd ⁡ \gcd gcd 相同的区间右端点更新答案即可
时间复杂度为 O ( n l o g 2 n ) O(nlog^2n) O(nlog2n)

具体细节见代码:

// Problem: P5502 [JSOI2015]最大公约数
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P5502
// Memory Limit: 256 MB
// Time Limit: 1000 ms
// 
// Powered by CP Editor (https://cpeditor.org)

//#pragma GCC optimize(2)
//#pragma GCC optimize("Ofast","inline","-ffast-math")
//#pragma GCC target("avx,sse2,sse3,sse4,mmx")
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<vector>
#include<set>
#include<map>
#include<stack>
#include<queue>
#include<unordered_map>
#define ll long long
#define inf 0x3f3f3f3f
#define Inf 0x3f3f3f3f3f3f3f3f
#define int  ll
#define endl '\n'
#define IOS ios::sync_with_stdio(0); cin.tie(0); cout.tie(0)
using namespace std;
int read()
{
	int res = 0,flag = 1;
	char ch = getchar();
	while(ch<'0' || ch>'9')
	{
		if(ch == '-') flag = -1;
		ch = getchar();
	}
	while(ch>='0' && ch<='9')
	{
		res = (res<<3)+(res<<1)+(ch^48);//res*10+ch-'0';
		ch = getchar();
	}
	return res*flag;
}
const int maxn = 1e6+5;
const int mod = 1e9+7;
const double pi = acos(-1);
const double eps = 1e-8;
int n,a[maxn],st[21][maxn],res;
void init(int n)
{
	for(int i = 1;i <= n;i++) st[0][i] = a[i];
	for(int i = 1;(1<<i) <= n;i++)
		for(int j = 1;j+(1<<i)-1 <= n;j++) st[i][j] = __gcd(st[i-1][j],st[i-1][j+(1<<(i-1))]);
}
int query(int l,int r)
{
	int lg = __lg(r-l+1);
	return __gcd(st[lg][l],st[lg][r-(1<<lg)+1]);
}
int find(int st,int l,int r,int val)
{
	int res = 0;
	while(l <= r)
	{
		int mid = l+r>>1;
		if(query(st,mid) == val) l = mid+1,res = mid;
		else r = mid-1;
	}
	return res;
}
signed main()
{
	n = read();
	for(int i = 1;i <= n;i++) a[i] = read();
	init(n);
	for(int i = 1;i <= n;i++)
		for(int l = i,r;l <= n;l = r+1)
		{
			int val = query(i,l);
			r = find(i,l,n,val);
			res = max(res,(r-i+1)*val);
		}
	cout<<res<<endl;
	return 0;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值