累積和の考えを利用する
連続する区間に、同じ数字を加える操作をします。これを繰り返すときは、累積和の考え方 (「競技プログラミングの鉄則」2.2 一次元の累積和(2) )が利用できます。
前の数字との差を表す配列を用意すると、 番目から連続する 個の要素に を加算することは、差の配列の 番目を , 番目を することに対応します。
与えられた数列のすべての要素を 0 にすることは、差の配列のすべての要素を 0 にすることと同じです。(差の配列の1番目の要素は、「前の数字」を 0 として、元の数列の1番目の値と同じにします。)
この問題では、連続要素の数 は固定していますので、要素の何番目かを表す添え字を で割った余りが異なる要素は互いに影響せず、独立に考えることができます。つまり、差の配列の要素を で割った余りが のグループに分けて、各グループですべて 0 にできるかを考えればよいです。
元の数列を
差の配列を
と表します。
この を端から 0 にする操作を行います。
まず、1番目の要素に を加算、 番目の要素に を加算する操作を行います。すると、 番目の要素は になります。
これを繰り返していくと、
番目の要素に を加算、 番目の要素に を加算する操作を行って、
番目の要素は になります。
そして、番目の要素に を加算、 番目の要素に を加算する操作を行って、
番目の要素は になります。
最終的に、後ろから K番目の要素まで 0 にすることが可能です。このとき、残りの 個の要素がすべて 0 になっていれば「良い数列」であり、 0 でないものが 1個でもあれば「良い数列」ではないことになります。
さらに、この条件は、
とも書けるため、
が のすべてのケースで同じ値になっていれば「良い数列」と言えます。
多数のクエリへの対応
この問題は、一つの数列に対して、範囲が異なる多数のクエリを処理します。そのため、さまざまな範囲の和が必要になります。
この部分も累積和 (「競技プログラミングの鉄則」2.1 一次元の累積和(1) )の考え方が利用できます。
として、 をあらかじめ計算しておきます。
配列の L 番目から R 番目について問われたとき、
を満たす と
を満たす
を用いて
がすべての で同じ値となっているか調べればよいです。
コード例 (Julia)
function solve() # 入力 readInts() = parse.(Int,split(readline())) readInt() = parse(Int,readline()) # 入力データ N,K = readInts() A = readInts() # K とばしの累積和 C = Vector{Int}(undef,N) C[1:K] = A[1:K] for i = K+1: N C[i] = A[i] + C[i-K] end # クエリ Q = readInt() save = -1 for i = 1:Q L, R = readInts() ans = "Yes" for p = 1: K s = cld(L-p,K) # 割り算,商の小数点以下切り上げ t = fld(R-p,K) # 割り算,商の小数点以下切り捨て if p + (s-1)*K > 0 sum = C[p + t*K] - C[p + (s-1)*K] else # 求める部分和が 先頭からの和なら C そのままでよい sum = C[p + t*K] end if p == 1 save = sum elseif sum != save # 和が一致しない場合があれば、良い配列ではない ans = "No" break end end println(ans) end end # function solve # main solve()