P5502 [JSOI2015]最大公约数 题解
简要题意:
- 给定一个长度为 \(n\) 的序列 \(a\),求出其中一个子串 \(S\),使得 \(|S| \times \gcd(x \in S )\). 求这个最大值。
- 给定一个长度为 \(n\) 的序列 \(a\),求出一个区间 \([l,r]\) 使得 \((r-l+1) \times \gcd_{i=l}^r a_i\) 最大.求这个最大值。
\(n \leq 10^5 , 1 \leq a_i \leq 10^{12}\).
两种不同表达的题意而已。
首先,抛出一个非常有用的结论:
- 最大值对应的区间长度不超过 \(\log n\).
为什么呢?
固定右区间 \(r\) ,枚举 \(l\) 向左拓展,每拓展一次,\(\gcd\) 要么不变,要么 \(\leq\) 原来 \(\gcd\) 的一半。 这样,不同的 \(\gcd\) 的值最多只有 \(\log n\) 个.
这样我们给定 \(r\) 用类似单调队列的方法维护即可。
时间复杂度:\(\mathcal{O}(n \log n \log a_i)\).
实际得分:\(100pts\).
#pragma GCC optimize(2)
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e6+1;
#define L (x<<1)
#define R (x<<1)+1
inline ll read(){char ch=getchar(); int f=1; while(ch<'0' || ch>'9') {if(ch=='-') f=-f; ch=getchar();}
ll x=0; while(ch>='0' && ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=getchar(); return x*f;}
inline void write(ll x) {
if(x<0) {putchar('-');write(-x);return;}
if(x<10) {putchar(char(x%10+'0'));return;}
write(x/10);putchar(char(x%10+'0'));
}
int n;
queue<int> q,qq;
ll a[N],ans=0;
inline ll gcd(ll n,ll m) {return !m?n:gcd(m,n%m);} //计算 gcd
signed main() {
n=read(); a[0]=-1;
for(int i=1;i<=n;i++) {
a[i]=read(); int l=0;
while(!q.empty()) {
int x=q.front(); q.pop(); //printf("%d\n",x);
a[x]=gcd(a[x],a[i]);
ans=max(ans,a[x]*(i-x+1)); //更新答案
if(a[x] == a[l]) continue;
qq.push(x); l=x; //新的决策点
} ans=max(ans,a[i]);
while(!qq.empty()) {
q.push(qq.front()); qq.pop();
} if(a[l] != a[i]) q.push(i);
} write(ans);
return 0;
}
简易的代码胜过复杂的说教。