【洛谷】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;
}