ARC067-F Yakiniku Restaurants
問題
軒の店があり、それぞれ番号の肉があり、美味しさが決まっている。 また、隣あう店を移動することができ、そのコストが決まっている。 訪れ方と肉の選び方を工夫することで、番号の肉を集めたときの(美味しさ合計-コスト)の最大値を求める。
気持ち
区間の店を訪れるとするとコストが決まるので考えやすい。 区間を固定すると、各肉で独立に考えることができ、 それぞれ区間内で一番美味しい肉をとってくれば良い。 ただこれだと計算量がとなって間に合わない。
どうにか状態をまとめられないか考えてみる。 当たり前なのだが、肉番号を固定したとき、区間ごとに選ばれる肉は最大でも種類しかない。 なので、各肉に対して「どの区間なら一番美味しいものとして選ばれるか」を考えると良さそう。
これはすなわち、「区間の他の要素がより小さい」ということになる。もう少し区間として要求される特徴を整理すると、
- をを満たす最小のindとしてとる。
- をを満たす最大のindとしてとる。
- 各と各の組み合わせが肉を選ぶ区間になる。
となる。つまり各に対しての情報さえあれば事足りる。これらはスタックを使って左右から走査することにより、全てのに対してで求めることができる。
で、残る問題は各の情報をどうやって消すかということになる。の情報が残っていると、後で区間を固定した時に結局計算量が増えてしまうためである。の情報を消したものとして、それっぽい以下を考える。
これに情報を貯めていければ、最後に区間固定して計算する際に区間を縮めながらやっていけばなんとかなりそうである。
ただ、にをそのまま足すのはまずい。区間の部分区間にはが含まれないものもあるからである。具体的にはまたはであるようなである(図を描いた方がわかりやすそうだが、めんどい)。 そのような「余計に足されてしまう区間」はとに集約される。なのでこいつらにを足しておけば後で差し引き0になる。
以上で解けた。計算量はである。計算量は増えるが、を求めるところはBIT使うのが自然かもしれない。
ACコード
上とはDPの定義が違って、dp[長さ][左端]にしている。そっちのが累積しやすそうだったからだが、あまり手間は変わらなさそう。
Submission #20276546 - AtCoder Regular Contest 067
def solve(N, M, A, B): INF = 10**18 # dp[length][left] dp = [[0]*(N+1-i) for i in range(N+1)] for m in range(M): # find segment Left = [-1]*N Right = [-1]*N stack = [(INF, 0)] for i in range(N): b = B[i][m] while stack[-1][0] < b: stack.pop() Left[i] = stack[-1][1] stack.append((b, i+1)) stack = [(INF, N)] for i in reversed(range(N)): b = B[i][m] while stack[-1][0] < b: stack.pop() Right[i] = stack[-1][1] stack.append((b, i)) for i, (l, r) in enumerate(zip(Left, Right)): b = B[i][m] dp[r-l][l] += b dp[i-l][l] -= b dp[r-i-1][i+1] -= b SA = [0] for a in A: SA.append(SA[-1]+a) ans = -INF for length in reversed(range(1, N+1)): for l in range(N+1-length): d = dp[length][l] ans = max(ans, d - (SA[l+length-1]-SA[l])) dp[length-1][l] += d dp[length-1][l+1] += d if length >= 2: dp[length-2][l+1] -= d return ans
感想
解説と似てたけどちょっと違ったので書いた。いもす法と言われてみるとそうなのかな。