LoginSignup
0
0

More than 1 year has passed since last update.

AtCoder ARC121 B - RGB Matching 後半パートの最小費用流による解法 MinCostFlow

Last updated at Posted at 2021-06-01

最小費用流を初めて使いました.

題意(省略)

こう考えた

まず、大枠の考え方は解説と同じです.$2N$の犬の各色がすべて偶数の場合、不満は起きないので答えは$0$です.
犬の色を変えても一般性を失わないので、Rを偶数匹、B,Gを奇数匹とします.この時、最小の値となるには、以下の最小です.

  • パターンA: BとGから不満が最小のペアを取る
  • パターンB: RとB、RとGの不満が合計が最小となるのペアを1つずつとる.この時、BとGがとるRは同じ犬ではいけない

まず、パターンAを考えるとこれは

  • 一方をソートして、もう一方の値を二分探索し、挿入できる配列の左右の要素とabsを取り、小さい方の値、 の最小値

です.例えば、$[2,4,7,8]$のとき、$5$を考えると、二分探索で4と7の間に挿入できますが、差は1と2なので1を考えます.この場合では、5は4とペアを組み、不満1が最小.と言えます.このように、もう一方の配列の値を$O(logN)$で検索することで、全体としては$O(MlogN)$でパターンAの値を求められます.

パターンBについて、結論としては、パターンAの応用でも解けます.RとG, RとBに対して、パターンAと同じ処理を行いそれぞれの結果をリストに記録し、この際、選ばれたRのインデックスを記録します.両方の結果、最小の値となるインデックスが同じであれば、片方を次点の値とすることとし、どちらの値を取るかを計算すれば良いです.

パターンBの最小費用流考察

今回のパターンBでは上記の考え方で良いですが、最小費用流を使って考えてみます.突然ですが、以下のようにグラフを作ります.以下「流せるフロー/フローごとのコスト」で示します.
この問題では過剰ですが、例えばRとGは2本、RとBは3本選んだときの最小値は?や、B,Gだけでなくもっと種類がある時など、グラフを作ればだけなので楽です.

  • 始点Sは各R(even)に対して1/0の辺を張ります.つまり、各Rは一度しか選べません
  • 各RはG(odd1)に対してパターンAと同じ処理を行い、「1/(Gのどれかと組み合わせる最小コスト)」を張ります.この時、Gのノードは1つずつ作っても良いですが、Gとして1つにまとめてしまって良いです.(つまり、$|G|$このノードは不要です)
  • 同じようにB(odd2)も辺を張ります.
  • G, Bでまとめたノードから1/0をTに張ります
  • この上でSからTに流量2を流そうとして、2流れたときのコストが答えです

このグラフはG,Bの各要素をノードとして生成しても解けます.その場合、G,BとTの間にはノードを作ってやり、1/0でに辺を貼って、G,Bが共に1までしか選べないようにする必要があります.この場合は以下のようにします.

実装

#include <bits/stdc++.h>
#include <atcoder/all>
#define FOR(i, begin, end) for(int i=(begin),i##_end_=(end);i<i##_end_;i++)
#define ALL(x) (x).begin(), (x).end()
#define REP(i, n) FOR(i,0,n)

#define FASTIO() cin.tie(0); ios::sync_with_stdio(false)

using namespace std;
using namespace atcoder;
const int numNode = 2*100000;
const int numColor = 4 * 100000;
const int numGraph = numNode + numColor + 100;
const int snode = numGraph - 3;
const int tnode = numGraph - 2;
using ll = long long;

// lからxに一番近い数字を探し距離(絶対値)を返す
// l, xはTを要求する
template <typename T>
T mindiffsearch(vector<T> &l, T x){
    auto sz = l.size();
    auto p = lower_bound(l.begin(), l.end(), x);
    auto indl = distance(l.begin(), p);
    if(indl == 0) return abs(x - l.at(0));
    if(indl == sz) return abs(x - l.at(sz-1));
    return min(abs(x - l.at(indl)), abs(x - l.at(indl - 1)));
}

int main(int argc, char *argv[]) {
    FASTIO();
    int n; cin >> n;
    vector<int> cnt(3);
    vector<vector<ll>> dat(3);
    REP(i, n*2){
        ll a; string b;
        cin >> a >> b;
        if(b.at(0) == 'R') {dat.at(0).emplace_back(a);      cnt.at(0)++; }
        else if(b.at(0) == 'G') {dat.at(1).emplace_back(a); cnt.at(1)++; }
        else if(b.at(0) == 'B') {dat.at(2).emplace_back(a); cnt.at(2)++; }
        else { assert(false); }
    }

    // 全部偶数のときは0が答え
    if( (cnt.at(0)%2 + cnt.at(1)%2 + cnt.at(2)%2) == 0){
        cout << 0 << "\n";
        return 0;
    }

    // 偶数を[0]にもっていく
    if(cnt.at(0)%2 == 0) {}
    else if(cnt.at(1)%2 == 0) {swap(cnt.at(0), cnt.at(1)); swap(dat.at(0), dat.at(1)); }
    else if(cnt.at(2)%2 == 0) {swap(cnt.at(0), cnt.at(2)); swap(dat.at(0), dat.at(2)); }
    else { assert(false); }

    REP(i, 3) sort(ALL(dat.at(i)));

    // 奇数1と奇数2から最小の差の値だけを得る
    ll res1 = 1e18;
    REP(i, cnt.at(1)){
        auto x = dat.at(1).at(i);
        res1 = min(res1, mindiffsearch(dat.at(2), x));
    }

    // 偶数と奇数1, 偶数と奇数2 を重複なく取る
    ll res2 = 1e18;
    {
        if (cnt.at(0) > 0) {
            int nodeS = 2 * n, nodeT = 2 * n + 1;
            int node1 = 2 * n + 2, node2 = 2 * n + 3;
            mcf_graph<int, long long> mcf(2 * n + 4);
            mcf.add_edge(node1, nodeT, 1, 0);
            mcf.add_edge(node2, nodeT, 1, 0);
            REP(i, cnt.at(0)) {
                mcf.add_edge(nodeS, i, 1, 0);
                mcf.add_edge(i, node1, 1, mindiffsearch(dat.at(1), dat.at(0).at(i)));
                mcf.add_edge(i, node2, 1, mindiffsearch(dat.at(2), dat.at(0).at(i)));
            }
            auto result = mcf.flow(nodeS, nodeT, 2);
            assert(result.first == 2);
            res2 = result.second;
            // フローを流した後の結果.これでflowをみると使った辺が復元できる
            //for (auto e: mcf.edges()) {
            //    cout << "s,t=" << e.from << "," << e.to << ", cap/cost"", " << e.cap << "," << e.cost << " flow:" << e.flow << "\n";
            //}
            //cout << "mcf:" << result.first << "-" << result.second << "\n";
        }
    }

    cout << min(res1, res2) << "\n";

}


0
0
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0