ECNU 3480 没用的函数 (ST表预处理 + GCD性质)
这题卡了双$log$的做法
令$gcd(a_{i}, a_{i+1}, a_{i+2}, ..., a_{j}) = calc(i, j)$
根据最大公约数的性质我们知道一个数和另一个数求$gcd$之后如果变小了,那么结果小于等于之前那个数的$1/2$
所以在考虑$a_{i}$的时候,
$calc(1, i), calc(2, i), calc(3, i), ..., calc(i, i)$这些数去重之后最多只有$logC$个不同的数
在考虑$a_{i}$之前把整个数列看成$logC$段,每一段先与$a_{i}$合并,然后再对每段分别求前缀和的最小值
(求出来的最小值是要被减去的)
分别更新答案即可。
注意特判$0$的情况,为了方便索性我把数列中的$0$都去掉了,出现$0$的话就把初始$ans$设成$0$
时间复杂度$O(nlogC), C = max(a_{i})$ (这里我忽略了$STL$自带的复杂度)
#include <bits/stdc++.h> using namespace std; #define rep(i, a, b) for (int i(a); i <= (b); ++i) #define dec(i, a, b) for (int i(a); i >= (b); --i) #define MP make_pair #define fi first #define se second typedef long long LL; typedef pair <LL, LL> PII; const int N = 1e6 + 10; const int A = 21; LL a[N], s[N], f[N][A]; LL ans; vector <PII> v1, v2; map <LL, LL> mp; int n, m, flag; int lg[N]; LL gcd(LL a, LL b){ return b == 0 ? a : gcd(b, a % b); } void init(){ rep(i, 1, n + 1) f[i][0] = s[i - 1]; rep(j, 1, 20) rep(i, 1, n + 1) if ((i + (1 << j) - 1) <= n + 1) f[i][j] = min(f[i][j - 1], f[i + (1 << (j - 1))][j - 1]); } inline LL calc(int l, int r){ if (l > r) return 0; int k = lg[r - l + 1]; return min(f[l][k], f[r - (1 << k) + 1][k]); } inline void solve(int x, int y){ if (!mp.count(x)) mp[x] = y; else mp[x] = min(mp[x], (LL)y); } int main(){ lg[1] = 0; rep(i, 2, 1e6 + 1) lg[i] = lg[i >> 1] + 1; scanf("%d", &n); m = 0; flag = 1; rep(i, 1, n){ LL x; scanf("%lld", &x); if (x) a[++m] = x; else flag = 0; } n = m; ans = 1ll * flag * (-9e18); rep(i, 1, n) s[i] = s[i - 1] + a[i]; init(); rep(i, 1, n){ LL cnt = abs(a[i]); mp.clear(); for (auto u : v1) solve(gcd(u.fi, cnt), u.se); solve(cnt, i); v1.clear(); for (auto u : mp) v1.push_back(MP(u.fi, u.se)); int sz = v1.size(); for (int j = 0; j < sz; ++j){ PII u = v1[j]; int r; if (j == sz - 1) r = i; else r = v1[j + 1].se - 1; ans = max(ans, (s[i] - calc(u.se, r)) * u.fi); } } printf("%lld\n", ans); return 0; }