Official

B - 山を下るハイカー / Hiker Descending the Mountain Editorial by MMNMM


高橋君が立ち寄るスポットの集合について、次のような性質が成り立つ必要があります。

  • すべてのスポットについて、標高は \(X\) 未満。
  • 立ち寄るどの \(2\) つのスポットも標高が異なる。

逆に、この性質が成り立つなら、うまく順番を定めることでそれらのスポットすべてに立ち寄ることができます。

よって、この問題は

  • 標高が \(X\) 未満のスポットを
  • どの \(2\) つのスポットも標高が異なるように

選んだときの景観スコアの合計を最大化する問題に帰着されます。

標高が \(A\) であるようなスポットを選ぶ場合、その中で景観スコアが最大であるものを選ぶべきです(標高が \(A\) である中で景観スコアが最大でないスポットを選んだ選び方は、そのスポットを標高が \(A\) である中で景観スコアが最大であるスポットに入れ替えることで景観スコアの合計がより大きくなります)。 よって、同じ標高である複数のスポットがある場合、その中で景観スコアが最大であるものを残してほかを削除しても答えは変わりません。

この操作によって、どのスポットも標高が異なるとすることができます。 ここから、この問題は標高が \(X\) 未満のスポットを好きに選んだときの景観スコアの合計を最大化する問題に帰着されます。 これは明らかに景観スコアが正であるスポットをすべて選べばよいです。

同じ標高のスポットのうち景観スコアが最大のものを残して残りを削除する操作は、連想配列などを使って「標高が \(A\) であるようなスポットの中で最大の景観スコア」を管理することで十分高速に行うことができます。 すべての標高に対して景観スコアが \(0\) であるスポットを追加することで、景観スコアの符号についての場合分けを減らすことができます。

実装例は以下のようになります。

#include <iostream>
#include <map>
#include <algorithm>
#include <ranges>
using namespace std;

int main() {
    int N, X;
    cin >> N >> X;

    // 連想配列を使って標高が A であるスポットの中の最大の景観スコアを管理する
    // 連想配列のアクセスしていない部分は 0 なので、仮想的に景観スコアが 0 のスポットが入っていると思うことができる
    map<int, int> scenic_score;
    for (int i = 0; i < N; ++i) {
        int A, B;
        cin >> A >> B;
        if (A < X) { // 標高が X 未満なら
            // 連想配列に追加する
            scenic_score[A] = max(scenic_score[A], B);
        }
    }
    // 景観スコアの合計が答え
    cout << ranges::fold_left(scenic_score | views::values, 0ul, plus{}) << endl;
    return 0;
}
N, X = map(int, input().split())

# 連想配列を使って標高が A であるスポットの中の最大の景観スコアを管理する
scenic_score = {}
for i in range(N):
    A, B = map(int, input().split())
    if A < X: # 標高が X 未満なら
        # 連想配列に追加する
        if A not in scenic_score: # 連想配列にまだ A が追加されていなければ
            scenic_score[A] = 0 # 仮想的に景観スコアが 0 であるスポットを追加しておく
        scenic_score[A] = max(scenic_score[A], B)

# 景観スコアの合計が答え
print(sum(scenic_score.values()))