UVa1642 魔法gcd Magical gcd

题目: UVa1642

solution

因为要求一个最优的子序列,可以想到枚举这个子序列的右端点\(j\)。 那么怎么快速算出左端点\(i\)的答案呢?
枚举每一个左端点,如果能知道这个子序列所有元素的\(gcd\)值就好了。
先考虑这样一个序列\(5,8,8,6,2\),假设现在\(j=4\),可以算出所有子序列对应的\(gcd\)。 1. \(i=1\), \(gcd(a_1,a_2,a_3,a_4)=1\) 2. \(i=2\), \(gcd(a_2,a_3,a_4)=2\) 3. \(i=3\), \(gcd(a_3,a_4)=2\) 4. \(i=4\), \(gcd(a_4)=6\)

注意到不同子序列的\(gcd\)值有可能是相等的,事实上\(gcd\)值的种类最多不会超过\(\log_2 a_j\)个,因为\(a_j\)的约数个数一定不多于\(\log_2 a_j\)
上表从下向上看,每次增加一个元素的时候,\(gcd\)值是不变或者减小的,而且变小时一定会变成\(a_j\)的一个约数。所以就维护每个左端点对应的区间\(gcd\)值(即\(gcd(a[i],a[i+1],...,a[j])\)),增加一个元素(即\(j\) -> \(j+1\))时,更新加上该元素后的区间\(gcd\)值就可以。
知道了当前每个左端点对应的\(gcd\),区间长度也是已知的,就可以计算答案了,对于每个区间的结果取一个最大值。
但是直接这样做复杂度是不对的,\(O(n^2log n)\)显然过不了。那怎么办呢。 我们可以发现,如果两个左端点的\(gcd\)相等,那么\(i\)更小的一定会更优,所以直接把劣的删除,对后面不造成影响。这样一来每次要枚举的\(i\)就和\(gcd\)的个数有关,复杂度是\(O(nlog^2 n)\),可以通过本题。

code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
const int N = 1e5 + 5;
ll n, a[N];
vector<pair<int, ll> > v;

ll read() {
ll w = 0; char ch = getchar();
while(ch > '9' || ch < '0') ch = getchar();
while(ch >= '0' && ch <= '9') {
w = w * 10 + ch - 48;
ch = getchar();
}
return w;
}
ll gcd(ll a, ll b) {
return b == 0 ? a : gcd(b, a % b);
}

int main() {
ll T = read();
while(T--) {
ll ans = 0; v.clear();
n = read();
for(int i = 1; i <= n; i++) a[i] = read();
for(int j = 1; j <= n; j++) {
v.push_back({j, a[j]});
for(int i = v.size() - 2; i >=0; i--) {
v[i].second = gcd(v[i].second, a[j]);
if(v[i].second == v[i + 1].second) v.erase(v.begin() + i + 1);
}
for(int i = 0; i < v.size(); i++) {
ans = max(ans, (j - v[i].first + 1) * (v[i].second));
}
}
printf("%lld\n", ans);
}
return 0;
}