予鈴

アウトプットとメモ書きの中間みたいな記事がたくさん出ます。

E - Traveler

問題

 N個のボールが与えられる。ボール iは、座標軸上 X_iに位置し、その色は C_iである。
座標軸を移動しながらボールの色が広義単調増加となるように回収する時、移動するコストを最小化せよ。
atcoder.jp

考察

色ごとにDPしたくなるが、各色と次の色の間ですべての推移を試すのは容易ではない。これは実際に推移する辺を貼ってみると、辺の数が非常に大きくなることからわかる。

そこで、同じ色のボールを回収する方法について試してみる。同じ色のボールを回収する始点と終点をうまく決めることができれば、推移の数を減らせるだろう。


 X_i < X_j < X_k < X_lとなる同じ色のボール i,j,k,lを考える
 X_j - X_i = \alpha, X_k - X_j = \beta,X_l - X_k = \gamma とおく。また、 dis(A,B)をAからBまでの距離を表すものとする。


まずは始点、終点がともに端点である場合を考えると、 dis(i,l) =  dis(l, i) = \alpha  + \beta + \gamma である。

つまり、同じ色のボールについて、端点(最小の座標軸にある点)から端点までボールを回収する場合は、途中で戻る必要がないため上記の式のようになる。

では、端点以外の点(例えば,  j,k)の場合を考えてみよう。 jを始点して、最初に iのボールを回収すると
 dis(j,i) + dis(i, l) > dis (i,l)となる。このような感じで、端点以外の頂点を始点とすると、端点から端点に移動する場合に比べてコストがかかることがわかる。


言い換えると、ボールを回収する最も効率的な動き方端点から端点で、そのコストは d(min(X), max(X))である


これより、各色のボールについて、端点から次のボールの色の端点に移動する場合を動的計画法で探索すれば良い。
 C,D, C < Dとして、各端点を l,r, nl, nrとすると l \rightarrow nl, l \rightarrow nr を試して最小値を取れば良い。

解法

#include <iostream>
#include <vector>
#include <algorithm>
#include <map>

using Int = long long;
using namespace std;

int main()
{
    cin.tie(nullptr);
    ios_base::sync_with_stdio(false);
    
    int N; cin >> N;
    map<Int,vector<Int>>mp;
    
    for(int i = 0; i < N; ++i)
    {
        Int x,c;
        cin >> x >> c;
        mp[c].push_back(x);
    }

    map<Int,Int>cnt;
    for(auto& itr : mp)
    {
        sort(itr.second.begin(),itr.second.end());
        Int prev = itr.second.front();
        for (const auto e : itr.second)
        {
            cnt[itr.first] += abs(e - prev);
            prev = e;
        }
    }
    vector<vector<Int>>dp(2, vector<Int>(mp.size(), 0LL));
    
    Int l = 0,r = 0;
    int i = 0;
    
    for(auto& itr : mp)
    {
        Int nl = itr.second.front();
        Int nr = itr.second.back();
        
        if(i)
        {
            dp[1][i] = min(dp[0][i - 1] + abs(nl - l), dp[1][i - 1] + abs(nl - r)) + cnt[itr.first];
            dp[0][i] = min(dp[1][i - 1] + abs(nr - r), dp[0][i - 1] + abs(nr - l)) + cnt[itr.first];
        }
        else
        {
            dp[1][i] = min(abs(nl - l), abs(nl - r)) + cnt[itr.first];
            dp[0][i] = min(abs(nr - r), abs(nr - l)) + cnt[itr.first];
        }
        
        l = nl;
        r = nr;
        
        if(i + 1 == mp.size())
        {
            dp[1][i] += abs(itr.second.back());
            dp[0][i] += abs(itr.second.front());
        }
        ++i;
    }
    
    cout << min(dp[0].back(), dp[1].back()) << endl;
}