以下の説明では, の約数の個数を と表現します.
以下の , を考えます.
: が最右である連続部分列で,最大公約数が となるようなものの部分列の長さの合計
: が最右である連続部分列で,最大公約数が となるようなものの個数
は が の約数でない場合は明らかに であるため, が求める答えです.よって, が求まっていれば, で答えを求めることができます.
次に,, を求める方法を考えます.
, について,以下の式が成り立ちます.
(ただし の場合はさらに )
(ただし の場合はさらに )
従って,
+= +
+=
と更新することで , が求まります., は が の約数でない場合は明らかに であるため,計算量は です.従って,, は で求まります.
ここまで,ある について, のうち でないものを緩く 個と見積もってきましたが,実はもう少し小さいオーダーで上から抑えることができます. は, 個の素因数を持ちます.部分列の長さを増やしていったとき,その区間の最大公約数の素因数の種類数,素因数の数はともに単調減少します.よって, のうち でないものは 個です.
以上より, で答えを求めることができます.
(解法は違いますが,類題として, の制約が と緩い代わりに,数列ではなく木上のパスで同じものを求めるabc248_gもこの機会に解いておきましょう.)
testerのあぷりしあです。D問題と同じように、セグ木上の二分探索を用いて解く解法を紹介します。
以下、 と定義します。
を固定して考えましょう。このとき、各 に対して、 は広義単調減少します。
の値が減少する境目の (つまり、 を満たす )を考えると、その個数は高々 の約数の個数となります。
そこで の各約数について、セグ木上の二分探索によって を求め、 についてまとめて計算することでかなり計算量を改善することができます。(Dの解説も参考にしてください。)
しかしながら、この計算量は ( は数列 の任意の要素、 はAの約数の個数)であり、 が最悪で 個もあるため間に合いません。
そこで、さらなる工夫として、境目が に対する をすべての約数について調べるのではなく、都度更新していくことを考えます。
最初、 として、以下を境目が配列の端に到達するまで繰り返します。
更新回数は、解法1と同様に の素因数の個数に着目することで であることがわかります。
以上より、 で答えを求めることができます。
xxxxxxxxxx
import math
from collections import defaultdict
MOD = 1000000007
n = int(input())
a = [*map(int, input().split())]
dp = dict()
dp[a[0]] = [1,1]
ans = a[0]
for i in range(1, n):
ndp = defaultdict(lambda: [0]*2)
for g in dp:
ndp[math.gcd(a[i], g)][0] += dp[g][0] + dp[g][1]
ndp[math.gcd(a[i], g)][1] += dp[g][1]
ndp[a[i]][0] += 1
ndp[a[i]][1] += 1
dp = ndp
for g in dp:
ans = (ans + g*dp[g][0])%MOD
print(ans)
xxxxxxxxxx
using namespace std;
using namespace atcoder;
long long gcd_e() {
return 0;
}
int main() {
int n;
cin >> n;
vector<long long> a(n);
for (long long &e: a) cin >> e;
segtree<long long, gcd, gcd_e> seg(a);
long long th;
auto f = [&](long long g) {
if (g == 0) return true;
return g >= th;
};
modint1000000007 ans = 0;
for (int i = 0; i < n; i++) {
long long l, r;
for (l = i + 1; l <= n; l = r + 1) {
th = seg.prod(i, l);
r = seg.max_right(i, f);
ans += (l + r - 2 * i) * (r - l + 1) / 2 * th;
}
}
cout << ans.val() << endl;
}