D - 工場の生産量 / Factory Production Volume Editorial by admin
Claude 4.6 Opus (Thinking)概要
\(N\) 個の工場の生産効率の積を素数 \(M\) で割った余りを求める問題で、各クエリでは1つの工場の値だけを変更した場合の答えを独立に計算する。
考察
素朴なアプローチの問題点
各クエリごとに \(N\) 個の値の積を計算すると、\(O(NQ)\) となり最大 \(4 \times 10^{10}\) 回の演算が必要で TLE になります。
重要な気づき
クエリで工場 \(X\) の値を \(V\) に変えたときの答えは:
\[\frac{\text{全体の積}}{P_X} \times V \pmod{M}\]
と書けます。\(M\) が素数なので、\(P_X\) が \(M\) の倍数でなければフェルマーの小定理で逆元を計算できます。
\(P_i\) が \(M\) の倍数の場合の問題
\(P_X \equiv 0 \pmod{M}\) のとき、\(P_X\) の逆元は存在しません。そこで場合分けが必要です。
核となるアイデア: \(P_i \bmod M = 0\) となる工場の個数(zero_count)と、それ以外の工場の積(product_nonzero)を事前に計算しておきます。
場合分け
変更対象の工場 \(X\) について、元の値 old = P[X] mod M と新しい値 v_mod = V mod M で場合分けします:
- old = 0 かつ v_mod = 0: ゼロの個数は変わらず、積は \(0\)
- old = 0 かつ v_mod ≠ 0: 他にまだゼロがあれば積は \(0\)、なければ
product_nonzero × v_mod mod M - old ≠ 0 かつ v_mod = 0: 積に \(0\) が含まれるので答えは \(0\)
- old ≠ 0 かつ v_mod ≠ 0: 他にゼロがあれば答えは \(0\)、なければ
product_nonzero × old⁻¹ × v_mod mod M
アルゴリズム
- 各 \(P_i\) を \(M\) で割った余り
P_mod[i]を計算する zero_count(\(P_i \bmod M = 0\) の個数)とproduct_nonzero(\(P_i \bmod M \neq 0\) であるものの積 mod \(M\))を前計算する- 各クエリで上記の場合分けに従って \(O(1)\) で(逆元計算は \(O(\log M)\) で)答えを求める
具体例: \(N=3\), \(M=5\), \(P = [2, 5, 3]\) の場合
- P_mod = [2, 0, 3]、zero_count = 1、product_nonzero = 6 mod 5 = 1
- クエリ「工場2を7に変更」→ old=0, v_mod=2, 他にゼロなし → 答え = \(1 \times 2 = 2\)
- クエリ「工場1を4に変更」→ old=2, v_mod=4, zero_count>0 → 答え = \(0\)
計算量
- 時間計算量: \(O(N + Q \log M)\)(前処理 \(O(N)\)、各クエリで逆元計算に \(O(\log M)\))
- 空間計算量: \(O(N)\)
実装のポイント
フェルマーの小定理により、\(M\) が素数のとき \(a^{-1} \equiv a^{M-2} \pmod{M}\)(ただし \(a \not\equiv 0\))。Pythonでは
pow(a, M-2, M)で高速に計算可能。\(V_j = 0\) のケースがあるため、
v_mod = 0の処理を忘れないこと。各シミュレーションは独立なので、元の配列を変更しないように注意する。
入出力が多いため、
sys.stdin.readlineや一括出力を使って高速化する。ソースコード
import sys
input = sys.stdin.readline
def main():
N, Q, M = map(int, input().split())
P = list(map(int, input().split()))
# Compute total product mod M
total_product = 1
for p in P:
total_product = total_product * p % M
# For each query, we need total_product / P[X-1] * V mod M
# Since M is prime, we can use Fermat's little theorem for modular inverse
# But if P[X-1] % M == 0, we can't compute inverse directly.
# Strategy: count how many P_i are divisible by M, and handle cases.
# Let zero_count = number of P_i where P_i % M == 0
# Let product_nonzero = product of (P_i % M) for all i where P_i % M != 0, mod M
P_mod = [p % M for p in P]
zero_count = 0
product_nonzero = 1
for pm in P_mod:
if pm == 0:
zero_count += 1
else:
product_nonzero = product_nonzero * pm % M
out = []
for _ in range(Q):
x, v = map(int, input().split())
x -= 1 # 0-indexed
v_mod = v % M
old = P_mod[x]
if old == 0:
# This element was contributing to zero_count
new_zero_count = zero_count - 1
# product without this element = product_nonzero * (product of other zeros=0 if new_zero_count>0)
if v_mod == 0:
# Replace 0 with 0: still zero_count zeros among others + this one = same
# All products: zero_count zeros total, result is 0
out.append(0)
else:
# new_zero_count zeros remain among other elements
if new_zero_count > 0:
# There are still other zeros, so product = 0 regardless of v
out.append(0)
else:
# No more zeros among others, product = product_nonzero * v_mod % M
out.append(product_nonzero * v_mod % M)
else:
# old != 0
if v_mod == 0:
# Adding a zero
new_zero_count = zero_count # zeros among other elements (unchanged)
# product = (product of others) * 0 = 0 if v_mod=0
# But wait, product of others might already be 0 if zero_count > 0
# Regardless, multiplying by 0 gives 0
out.append(0)
else:
# Neither old nor new is zero mod M
if zero_count > 0:
# There are zeros among other elements (since old != 0, removing old doesn't change zero_count)
# product of all others includes zeros -> 0, times v_mod -> 0
out.append(0)
else:
# No zeros anywhere, straightforward
# product_without_x = product_nonzero * modinv(old) % M
# answer = product_without_x * v_mod % M
inv_old = pow(old, M - 2, M)
ans = product_nonzero * inv_old % M * v_mod % M
out.append(ans)
sys.stdout.write('\n'.join(map(str, out)) + '\n')
main()
この解説は claude4.6opus-thinking によって生成されました。
posted:
last update: