【洛谷】P7009 [CERC2013] Magical GCD

原题链接

题意

\(T\) 组询问,每次给出 \(n\) 个数 \(a_i\)

你需要找到这个数组的一个子序列(要求编号连续),使得该序列中所有数的最大公约数和序列长度的乘积最大,并输出这个最大值。

数据范围

\(1 \leq n \leq 10^5,1 \leq a_i \leq 10^{12}\)

思路

参考了这篇博客

对于原序列的一个子区间,如果固左端点 \(l\) 不断往右延伸,设当前区间的最大公约数为 \(g\),现在要把元素 \(x\) 扩展进来,新区间的最大公约数要么还是 \(g\),要么就至少减少一半(根据 \(gcd\) 的性质),那么区间的 \(gcd\) 就至多更新 \(\log a_l\)

因为题目所求的是最大值,那么对于 \(gcd\) 相同的区间,肯定会选择长度最长的区间。那么就可以从前往后枚举 \(a_i\),用一个队列 \(q\) 记录这些不同的左端点,同时用 \(a_i\) 去更新这些左端点,并同时更新答案。

code:

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
const int N=1e5+10;
#define LL long long
queue<int>q,r;
int n;LL a[N],res;
LL gcd(LL a,LL b){return b==0?a:gcd(b,a%b);}
int main()
{
	int T;scanf("%d",&T);
	while(T--)
	{
		scanf("%d",&n);res=0;
		for(int i=1;i<=n;i++)
		{
			scanf("%lld",&a[i]);LL last=0;res=max(res,a[i]);
			while(!q.empty())
			{
				int x=q.front();q.pop();a[x]=gcd(a[x],a[i]);//x就是最小的满足区间[x,i]的gcd为a[x]的左端点 
				res=max(res,a[x]*(i-x+1));
				if(a[x]==a[last]) continue;//一些左端点就可以合并掉 
				r.push(x);last=x;
			}
			while(!r.empty())
			{
				q.push(r.front());
				r.pop();
			}
			if(a[last]!=a[i]) q.push(i);//i也可以作为一个新的左端点 
	 	}
		printf("%lld\n",res);
		while(!q.empty()) q.pop();
	}
	return 0;
}
posted @ 2022-07-23 08:20  曙诚  阅读(61)  评论(0)    收藏  举报