题解 P5502 【[JSOI2015]最大公约数】
UPD: 19/10/05 补充了部分说明,感谢 @LRL52 提出的错误并修正了代码。
UPD:21/06/10 修改了表述并补充了正确性证明
这题可以用区间分治的思想。在两年后的今天我才惊讶地发现《算法导论》分治章节的第一页就是这个算法。
题目分析:
在我们现在考虑的区间
前两种情况递归就行了,边界为
然后是第三种情况,既然
在加进新数改变
不算 大家就把gcd当常数好了
代码实现(2019年,216ms in total):
//2019 last edition by saxiy,2019的古董仅作参考
#pragma GCC optimize("fast-math,unroll-loops")
#include <bits/stdc++.h>
#define RS register
#define N 100005
using namespace std;
typedef long long ll;
template <typename T> void read(T &x) {
x = 0; RS char c(getchar());
while(!isdigit(c)) c = getchar();
for(;isdigit(c);c = getchar())
x = (x << 3) + (x << 1) + (c ^ 48);
}
int n; ll a[N];
inline ll max(ll a, ll b) { return a > b ? a : b; }
ll gcd(ll a, ll b) {//不用管这gcd
if(!a) return b; if(!b) return a;
if(!(a & 1 || b & 1))
return gcd(a >> 1, b >> 1) << 1;
if(!(a & 1)) return gcd(a >> 1, b);
if(!(b & 1)) return gcd(a, b >> 1);
return gcd(abs(a - b), min(a, b));
}
ll dfs(int L, int R) {
if(L == R) return a[L];
RS int l, r, mid = L + R >> 1;
l = r = mid;
RS ll g = a[mid], maxx = a[mid];
while(L <= l && r < R) {
g = gcd(a[++r], g);
while(r <= R) if(a[++r] % g) break; r--;
while(l >= L) if(a[--l] % g) break; l++;
maxx = max(maxx, (r - l + 1) * g);
}
l = r = mid; g = a[mid];
while(L < l && r <= R) {
g = gcd(a[--l], g);
while(l >= L) if(a[--l] % g) break; l++;
while(r <= R) if(a[++r] % g) break; r--;
maxx = max(maxx, (r - l + 1) * g);
}
return max(maxx, max(dfs(L, mid), dfs(mid + 1, R)));
}
int main() {
read(n);
for(RS int i = 1;i <= n;i++) read(a[i]);
printf("%lld", dfs(1, n));
return 0;
}
正确性证明
会用到几个易证的引理,留给读者思考就略过证明了。
- 对于所有
a_i(i \in [L,R]) ,区间[a_i,a_{mid}] 一定被拓展过。 - 对于一个序列任意的子序列(可以不连续),子序列的
\gcd 一定\ge 原序列的\gcd 。 - 如果公共
\gcd 减小,至少除以2 。
定义某区间的 父区间 为此区间经两侧完全贪心拓展后的区间。
定义 最大价值区间 为其相同公共
现在我们算法最大的问题就是只能保证每个
假设存在 最大价值区间
定义区间
不等式
将不等式
相加便和不等式
所以,不存在这样的区间有机会更新答案
证毕,这就是正确性。