コンテンツにスキップ

累積和

タグ「累積和」の記事 2 件

Blitz Round #25 C - Root Value

正の整数 xx に対して、xm\sqrt[m]{x} が自然数になるような最大の mm を f(x)f(x) とします。

長さ nn の数列 a1,…,ana_1, \ldots, a_n (2≤ai≤2002 \le a_i \le 200) と qq 個のクエリ (l,r)(l, r) が与えられるので、各クエリについて

f(∏i=lrai)f\left(\prod_{i=l}^{r} a_i\right)

を求める問題です。

  • 1≤n,q≤2⋅1051 \le n, q \le 2 \cdot 10^5、テストケースの nn の総和と qq の総和はそれぞれ 2⋅1052 \cdot 10^5 以下

正確な問題文は元の問題ページを見てください。

xx を素因数分解して x=p1e1p2e2⋯pkekx = p_1^{e_1} p_2^{e_2} \cdots p_k^{e_k} とすると、xm\sqrt[m]{x} が整数になるのは、すべての指数 eje_j が mm の倍数のときです。したがって

f(x)=gcd⁡(e1,e2,…,ek)f(x) = \gcd(e_1, e_2, \ldots, e_k)

です。ai≥2a_i \ge 2 なので、積は 22 以上になり、指数のどれかは正になります。

積をそのまま計算すると巨大になりますが、指数だけを見れば足し算になります。ai≤200a_i \le 200 なので、現れる素数は 200200 以下の 4646 個だけです。各素数について指数の累積和を持っておけば、区間 [l,r][l, r] での各素数の指数は差を取るだけで求まり、その gcd⁡\gcd が答えです。

aia_i の上限を AA (=200= 200)、AA 以下の素数の個数を π(A)\pi(A) (=46= 46) とすると、計算量は O((n+q) π(A))O((n + q)\,\pi(A)) です。

Python
from math import gcd
prime = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199]
for _ in range(int(input())):
N, Q = map(int, input().split())
A = list(map(int, input().split()))
acc = [[0] * len(prime)]
for a in A:
t = acc[-1].copy()
for i, p in enumerate(prime):
while a % p == 0:
t[i] += 1
a //= p
acc.append(t)
for _ in range(Q):
l, r = map(int, input().split())
l -= 1
ans = 0
for i in range(len(prime)):
ans = gcd(ans, acc[r][i] - acc[l][i])
print(ans)

Blitz Round #25 B - Jump Trip Score

長さ nn の整数列 a1,…,ana_1, \ldots, a_n があり、位置 i=1i = 1 から出発して i=n+1i = n + 1 に着いたら終わりです。スコアは 00 から始まり、各位置で次のどちらかを行います。

  • aia_i をスコアに足して i+1i + 1 に進む
  • 正の整数 dd (i+d≤n+1i + d \le n + 1) を選んで i+di + d に跳ぶ(スコアは変わらない)

跳ぶ操作は高々 1 回まで使えるとき、最終スコアの最大値を求める問題です。

  • 1≤n≤4⋅1051 \le n \le 4 \cdot 10^5、−109≤ai≤109-10^9 \le a_i \le 10^9、テストケースの nn の総和は 4⋅1054 \cdot 10^5 以下

正確な問題文は元の問題ページを見てください。

ii から i+di + d に跳ぶと、ai,…,ai+d−1a_i, \ldots, a_{i+d-1} を飛ばすことになります。跳ぶのは高々 1 回なので、得られるスコアは

(先頭からの連続部分の和)+(末尾までの連続部分の和)(\text{先頭からの連続部分の和}) + (\text{末尾までの連続部分の和})

で、間の 1 区間を飛ばした形になります(跳ばなければ全部の和)。

そこで

  • LkL_k = 長さ kk 以下の接頭辞の和の最大値(空も含むので 00 以上)
  • RkR_k = 位置 k+1k + 1 以降から始まる接尾辞の和の最大値(空も含む)

を累積和で求めておけば、答えは

max⁡0≤k≤n(Lk+Rk)\max_{0 \le k \le n} (L_k + R_k)

です。接頭辞と接尾辞が重ならないように、境目 kk を全通り試しています。計算量は O(n)O(n) です。

Python
for _ in range(int(input())):
N = int(input())
A = list(map(int,input().split()))
dp0 = [0]
x = 0
for a in A:
x += a
dp0.append(max(x, dp0[-1]))
dp1 = [0]
x = 0
for a in reversed(A):
x += a
dp1.append(max(x, dp1[-1]))
dp1.reverse()
print(max(x+y for x,y in zip(dp0,dp1)))

dp0 が LL、dp1 が RR にあたります。