Official

D - 均等な買い物 / Equal Shopping Editorial by harurun4635


簡単には \(O(N^{P+Q})\) 通りの選び方がありますが、これをすべて試すことはできません。しかし、結局はほしいものは subset sum で、値の範囲が小さいですから dp が有効であると考えられます。

\(dp[i][j][C_i][C_j] =\) 高橋くんは \(i\) 個購入し \(C_i\) 円、青木くんは \(j\) 個購入し \(C_j\) 円できる通り数

とした next dp では、状態が \(O(N \times P\times Q \times PR \times QR)\) 遷移が \(O(R)\) で、これを \(M\) 回解きます。

間に合うかの評価は少し大変ですが、額面では \(1.296 \times 10^9\) 程度になり、十分に高速な言語なら(特別な工夫なしに)間に合うでしょう。


しかし、簡単な改善があります。今回は、最終的に \(C_i = C_j\) にしか興味がないため、 \(D = C_i - C_j\) として \(dp[i][j][D]\) のかたちで管理すればよいです。

こうすることで、状態が \(O(N \times \ P \times Q \times (P + Q) R)\) に抑えることができます。これは、額面では \(10\) 倍程度の改善で、実行時間に余裕を持って間に合うでしょう。


実装例

Python では、負の index をもちいることができることを利用しています。( dp[-x] = dp[len(dp)-x])言語によっては、適切にこの式で書き直すか、 すべてに offset を加えることで、常に添字を \(0\) 以上にするなどの工夫が必要です。

どちらの場合においても、配列外参照については、十分に注意しなければならないと思われます。

mod = 998244353
n, p, q, m = map(int, input().split())
lr = [list(map(int, input().split())) for _ in range(n)]

def solve():
    dp = [[[0] * 130 for _ in range(q + 1)] for _ in range(p + 1)]
    dp[0][0][0] = 1
    for l, r in lr:
        ndp = [[[0] * 130 for _ in range(q + 1)] for _ in range(p + 1)]
        for i in range(p + 1):
            for j in range(q + 1):
                for k in range(-60, 61):
                    v = dp[i][j][k] % mod
                    if v == 0: continue
                
                    ndp[i][j][k] += v
                    
                    if i < p:
                        for x in range(l, r + 1):
                            ndp[i+1][j][k+x] += v
                    if j < q:
                        for x in range(l, r + 1):
                            ndp[i][j+1][k-x] += v
        dp = ndp

    return dp[-1][-1][0] % mod

for _ in range(m):
    x, a, b = map(int, input().split())
    lr[x-1] = [a, b]
    print(solve())

posted:
last update: