ARMERIA

Rubyと競技プログラミングの話 AtCoderやCodeforcesの問題解説記事が多め。

AtCoder Regular Contest 077 E - guruguru

お題箱より。

E - guruguru

後半は公式解説と少し違う方法になっています。

解法

お気に入りで節約できる回数を定式化する

ある1つの  i に対する  a_{i} から  a_{i+1} への移動を考えます。まず簡単のため、 a_{i} \lt a_{i+1} と仮定しておきます。移動元と移動先をそれぞれ  s_{i} = a_{i}, t_{i} = a_{i+1} と書くことにします。

お気に入りボタンを使わずに  s_{i} から  t_{i} に移動するボタン回数は  t_{i}-s_{i} です。ここで「 t_{i}-s_{i} と比べて、お気に入りボタンがもし値  x に設定されていたら回数をどれだけ節約できるか?」という値をそれぞれの  x について考えましょう。この値を  f_{i}(x) と書くことにします。

これは以下のように、 s_{i} + 2 \le x \le t_{i} の範囲において  1, 2, ... という等差数列になり、他は0になります。

f:id:betrue12:20191019155240p:plain

ただし  a_{i} \gt a_{i+1} のときは途中で明るさがループします。これを扱うのは面倒なので、「ループするときは2周回す」テクニックを使います。

 a_{i} \gt a_{i+1} のときは移動先  t_{i} は2周目にあると考えて、 s_{i} = a_{i}, t_{i} = a_{i+1} + m とします。そして  f_{i}(x) も2周分、つまり  1 \le x \le 2m の範囲で値を持つことにします。このときお気に入りが  x に設定されている時の節約回数は、1周目と2周目を合わせて  f_{i}(x) + f_{i}(x+m) と求めればよいです。

これを  n-1 回の移動について全て足したもの、つまり  \sum_{i}f_{i}(x) + \sum_{i}f_{i}(x+m) が「お気に入りを  x に設定したときに、お気に入りを全く使わない時に比べて節約できる合計回数」です。この最大値を、お気に入りを全く使わない時の合計回数  \sum_{i} (t_{i} - s_{i}) から引いたものが答えになります。

こうなると残る課題はそれぞれの  \sum_{i}f_{i}(x) を求めること。つまり「数列に『ある区間だけに等差数列を足す』という操作をいくつも行った結果」を効率的に求めることになりました。

等差数列の重ね合わせを求める

ここで使うのは「階差数列」を利用するテクニックです。

話が変わりますが、「いもす法」というテクニックがあります。1次元のいもす法は「ある区間だけに同じ値を足す」という操作をいくつも行った結果を効率的に求めるテクニックです。これは以下のように、階差数列を利用しているテクニックと思うことが出来ます。

f:id:betrue12:20191019163129p:plain

「ある区間だけに同じ値があり、区間外は全て0」という数列は、1階の階差数列を取ると上記のように0でない値が2箇所だけになります。

「階差数列を取る」という操作と「累積和を取る」という操作はちょうど逆の操作になっていて、かつこれらの操作には線型性があるので、足したい数列たちの階差数列を足してから累積和を取ることで元の数列たちの和が得られます。

同様のことが等差数列でもできます。今回の  f_{i}(x) のように「ある区間だけに等差数列があり、区間外は全て0」という数列は、2階の階差数列を取ることで0でない値が3箇所だけになります。

f:id:betrue12:20191019163416p:plain

よって  n-1 回の全ての移動についてこれらの値を足した後、2回累積和を取ることで全ての  x に対する  \sum_{i}f_{i}(x) を求めることができます。

ACコード

Submission #7983845 - AtCoder Regular Contest 077