分析
我们换一个题面,其实是要我们求 。那么我们发现这个
的出现太突兀了,考虑用枚举这个
。那么我们枚举
之后,就暴力枚举这个区间就可以了。为什么呢?我们可以证明
只会有
种取值。那么我们完全可以再枚举一个
,又因为
的大小是具有单调性的,所以二分枚举边界就可以了。关键是
的枚举,我们可以采用笛卡尔树来做,也可以使用
的
的建树。这里才用了单调栈的
建树,总的复杂度为
。轻松最优解。
代码
#include<bits/stdc++.h>
using namespace std;
int read() {
int x = 0,f = 0;char ch = getchar();
while(!isdigit(ch)) {if(ch=='-')f=1;ch=getchar();}
while(isdigit(ch)) {x = x * 10 + ch - '0';ch = getchar();}
return f ? -x : x;
}
const int N = 2e5 + 1000,mod = 1e9 + 7;
int A[N],top,rc[N],lc[N],st[N],n,Ans = 0;
int L[N],siL[N],R[N],siR[N],S[21][N],Log[N];
int gcd(int a,int b) {return b ? gcd(b,a % b) : a;}
int get(int l,int r) {
int k = Log[r - l + 1];
return gcd(S[k][l],S[k][r - (1 << k) + 1]);
}
void solve(int u,int l,int r) {
if(lc[u]) solve(lc[u],l,u-1);if(rc[u]) solve(rc[u],u+1,r);
int x = u,y = u;L[0] = 0;R[0] = 0;siL[0] = 0;siR[0] = 0;
while(l <= x) {
L[++L[0]] = get(x,u);
int ll = l,rr = x,ans = x;
while(ll <= rr) {
int mid = ll + rr >> 1;
if(get(mid,u) == L[L[0]]) rr = mid - 1,ans = min(ans,mid);
else ll = mid + 1;
}
siL[++siL[0]] = x - ans + 1;x = ans - 1;
}
while(y <= r) {
R[++R[0]] = get(u,y);
int ll = y,rr = r,ans = y;
while(ll <= rr) {
int mid = ll + rr >> 1;
if(get(u,mid) == R[R[0]]) ll = mid + 1,ans = max(ans,mid);
else rr = mid - 1;
}
siR[++siR[0]] = ans - y + 1;y = ans + 1;
}
int res = 0;
for(int i = 1;i <= L[0];i++) {
for(int j = 1;j <= R[0];j++) {
int tot = gcd(L[i],R[j]);
res = (res + 1LL * tot * siL[i] % mod * siR[j] % mod) % mod;
}
}
Ans = (Ans + 1LL * res * A[u] % mod) % mod;
}
signed main() {
n = read();
for(int i = 1;i <= n;i++) A[i] = read(),S[0][i] = A[i];
for(int i = 2;i <= n;i++) Log[i] = Log[i >> 1] + 1;
for(int i = 1;i <= n;i++) {
int k = top;
while(k && A[st[k]] < A[i]) k--;
if(k) rc[st[k]] = i;
if(k < top) lc[i] = st[k + 1];
st[++k] = i;top = k;
}
for(int i = 1;i <= 19;i++) {
for(int j = 1;j + (1 << i) - 1 <= n;j++) {
S[i][j] = gcd(S[i-1][j],S[i-1][j + (1 << i - 1)]);
}
}
solve(st[1],1,n);
cout << Ans << endl;
return 0;
}
京公网安备 11010502036488号