C - 均等な荷分け / Equal Load Distribution Editorial by admin
GPT 5.2 High(計算量が小さいことの証明が不十分)概要
荷物列を順番を崩さずにいくつかの連続区間に分割し、各区間(各荷台)の総重量がすべて等しくなるようにするとき、使える荷台の個数の最大値を求めます。
考察
重要な気づき
荷台の個数を \(k\) 個とすると、各荷台の総重量は - 全体の総和を \(T=\sum H_i\) として - 各荷台の重さ \(S = T/k\)
となる必要があります。よって \(k\) は \(T\) の約数でなければなりません。
さらに、荷物は連続区間で分けるので、区切り位置は「累積和」で表せます。
累積和を \(P_0=0,\,P_i=H_1+\cdots+H_i\) とすると、重さ \(S\) の区間を \(k\) 個作るには
- \(S, 2S, 3S, \ldots, (k-1)S\)
がすべて累積和集合 \(\{P_0,P_1,\ldots,P_N\}\) の中に存在することが必要十分です。
(これらが存在すれば、その位置で区切ることで各区間和がすべて \(S\) になります。)
また、どの区間にも少なくとも1個の荷物が入るので \(k \le N\)。
加えて、各区間和 \(S\) は最大の荷物重量 \(\max(H_i)\) 以上でないと不可能なので
- \(S \ge \max(H_i)\)
- つまり \(T/k \ge \max(H_i) \Rightarrow k \le T/\max(H_i)\)
が成り立ちます。
以上より、調べるべき \(k\) は - \(k \mid T\) - \(k \le \min\!\left(N,\left\lfloor T/\max(H_i)\right\rfloor\right)\)
に絞れます。
素朴解が厳しい理由
「\(k\) を大きい方から順に全探索」して毎回配列を走査すると、最悪で \(O(N^2)\) 近くになり間に合いません。
そこで、
- 候補の \(k\) を「\(T\) の約数」に限定(数が少ない)
- 判定は走査ではなく「累積和の集合に含まれるか」を見る(1回の判定が軽い)
という方針にします。
アルゴリズム
- 全体和 \(T=\sum H_i\) と最大値 \(M=\max(H_i)\) を求める。
- 累積和 \(P_i\) を作り、すべてを集合
prefに入れる(0も入れる)。 - \(T\) を素因数分解し、全約数を列挙する。
- 約数のうち \(k \le k_{\max}=\min(N,\lfloor T/M\rfloor)\) を満たすものだけ候補にし、大きい順に試す。
- 候補 \(k\) について \(S=T/k\) とし、
S, 2S, ..., (k-1)Sがすべてprefに含まれるかを集合検索で確認する。- すべて含まれれば、その \(k\) が最大なので出力して終了。
- どれもダメなら必ず \(k=1\) は可能なので
1を出力。
(例) - \(H=[1,2,3,3,2,1]\) なら \(T=12\)。 - \(k=4\) のとき \(S=3\)。累積和は \(0,1,3,6,9,11,12\) なので \(3,6,9\) が存在しOK → 4分割可能。
計算量
- 時間計算量:
- 素因数分解: \(O(\sqrt{T})\)
- 累積和作成: \(O(N)\)
- 約数個数を \(d\) とすると、判定は合計で概ね \(O\!\left(\sum_{k\in \text{candidates}} k\right)\)(各候補 \(k\) につき最大 \(k-1\) 回の集合検索)
- まとめて \(O(\sqrt{T}+N+\sum k)\)
- 素因数分解: \(O(\sqrt{T})\)
- 空間計算量: \(O(N)\)(累積和集合)
実装のポイント
累積和を
setに入れることで、「区切り位置が存在するか」を \(O(1)\) 平均で判定できます。先に \(k_{\max}=\min(N,\lfloor T/\max(H_i)\rfloor)\) で候補を絞ると無駄な試行が減ります。
約数は大きい順に試し、最初に成功したものを出力すれば「最大個数」が保証されます。
入力が大きいので
sys.stdin.buffer.read()で高速に読みます。ソースコード
import sys
import math
def factorize(n: int):
factors = []
cnt = 0
while n % 2 == 0:
n //= 2
cnt += 1
if cnt:
factors.append((2, cnt))
p = 3
while p * p <= n:
if n % p == 0:
cnt = 0
while n % p == 0:
n //= p
cnt += 1
factors.append((p, cnt))
p += 2
if n > 1:
factors.append((n, 1))
return factors
def all_divisors_from_factors(factors):
divs = [1]
for p, e in factors:
cur = []
pe = 1
for _ in range(e + 1):
for d in divs:
cur.append(d * pe)
pe *= p
divs = cur
return divs
def main():
data = list(map(int, sys.stdin.buffer.read().split()))
N = data[0]
H = data[1:]
total = sum(H)
max_h = max(H)
pref = set()
s = 0
pref.add(0)
for h in H:
s += h
pref.add(s)
kmax = min(N, total // max_h) # S = total/k must satisfy S >= max_h
factors = factorize(total)
divs = all_divisors_from_factors(factors)
candidates = [k for k in divs if k <= kmax]
candidates.sort(reverse=True)
for k in candidates:
seg = total // k
if seg < max_h:
continue
x = seg
ok = True
for _ in range(k - 1):
if x not in pref:
ok = False
break
x += seg
if ok:
print(k)
return
print(1)
if __name__ == "__main__":
main()
この解説は gpt-5.2-high によって生成されました。
posted:
last update: