2016-2017 ACM-ICPC Pacific Northwest Regional Contest (Div. 1) - G: Maximum Islands ★★★☆☆

評価・感想

フローと塗りつぶしの2ステップがあるけどそこまで実装重くなく, 教育的良問だと思った.
二部グラフの最大安定集合は二部グラフの最小点カバーになって最小カットになって最大流になってすごく楽しい.

状態は2つ?最小カット?でも最大化したい?は?双対??
と混乱していたがトイレに行って整理したら自明だった.
落ち着いて整理して問題解こうな.
そして最大流のライブラリがバグっててTLEった.
DAGはメモ化しようとここ数ヶ月で114514回言ってる.

問題

H×Wのグリッドがあり, 各マスはL(陸地), W(海), C(不明)である.
CをLかWに割り当てて島の数を最大化したい.
陸地は4近傍で隣接し, その連結成分を島とする.
codeforces.com

制約

  • W, H \le 40

入力例

5 4
LLWL
CCCC
CCCC
CCCC
LWLL

出力例

8

考察

  • すでにわかっているLの周り(4近傍)のCはWが最適
  • 答え = そのときの島の数 + 残りのCをいい感じに割り当てたときにできた島の数
  • 市松模様で良さそうに見えるけど実はダメ
WCWWCW
CCCCCC
WCWWCW
  • 残りのCから生まれたどの島も広さ1が最適
  • つまり, 次の問題になった

残りのCは4近傍で辺を張ると, 二部グラフで, 隣り合わないようにいくつかの頂点を選び, 選んだ頂点数を最大化したい

  • これは最大安定集合問題
  • 二部グラフなのでフローで解ける
  • DinicでO(|E|\sqrt{|V|})なので40^3となり間に合う

解答

#include <bits/stdc++.h>

using namespace std;

typedef long long ll;
typedef pair<ll,ll> P;

const ll linf = 1e18;

#define FOR(i,a,b) for(ll i = (a); i < (b); ++i)
#define RFOR(i,a,b) for(ll i = (b)-1; i >= (a); --i)
#define REP(i,n) FOR(i,0,n)
#define RREP(i,n) RFOR(i,0,n)
#define EACH(i,v) for(auto&& i : v)
#define ALL(v) (v).begin(),(v).end()
#define pb push_back

template<class T>
istream& operator>>(istream& is, vector<T>& v) {
    EACH(i, v) is >> i;
    return is;
}
template<class T>
ostream& operator<<(ostream& os, const vector<T>& v) {
    EACH(i, v) {
        if (i) os << " ";
        os << i;
    }
    return os;
}

class MaxFlow {
public:
    struct Edge {
        ll to, cap, rev;
    };
private:
    ll V;
    vector<vector<Edge>> G;
    vector<bool> used;
    vector<ll> bfs(ll s) {
        vector<ll> dist(V, linf);
        dist[s] = 0;
        queue<ll> Q; Q.push(s);
        while ( !Q.empty() ) {
            ll v = Q.front(); Q.pop();
            EACH(e, G[v]) {
                if (e.cap > 0 && dist[e.to] == linf) {
                    dist[e.to] = dist[v]+1;
                    Q.push(e.to);
                }
            }
        }
        return dist;
    }
    ll dfs(ll v, ll t, ll f, const vector<ll>& dist) {
        if (v == t) return f;
        if (used[v]) return 0;
        used[v] = true;
        EACH(e, G[v]) {
            if (e.cap > 0 && dist[e.to] == dist[v]+1) {
                ll d = dfs(e.to, t, min(f, e.cap), dist);
                if (d > 0) {
                    e.cap -= d;
                    G[e.to][e.rev].cap += d;
                    return d;
                }
            }
        }
        return 0;
    }
public:
    const vector<vector<Edge>> Graph() {
        return G;
    }
    MaxFlow(ll V) : V(V), G(V) {}
    void init(ll n) {
        V = n;
        G.assign(V, vector<Edge>());
    }
    void add(ll from, ll to, ll cap) {
        assert(V > 0);
        G[from].pb({to, cap, (ll)G[to].size()});
        G[to].pb({from, 0, (ll)G[from].size()-1});
    }
    ll flow(ll s, ll t, ll f=linf) {
        ll res = 0;
        while (f > 0) {
            // cout << "BFS Start" << endl;
            vector<ll> dist = bfs(s);
            // cout << "BFS End" << endl;
            if (dist[t] == linf) break;
            while (f > 0) {
                // cout << "DFS Start" << endl;
                used.assign(V, false);
                ll df = dfs(s, t, f, dist);
                // cout << "DFS End" << endl;
                if (df == 0) break;
                f -= df;
                res += df;
                // cout << res << endl;
            }
        }
        return res;
    }
};

const ll dx[4] = {0, 1, 0, -1};
const ll dy[4] = {-1, 0, 1, 0};

int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    ll h, w; cin >> h >> w;
    auto inRange = [&](ll x, ll y) {
        return 0 <= x && x < w && 0 <= y && y < h;
    };
    vector<string> m(h); cin >> m;
    REP(y, h) REP(x, w) {
        if (m[y][x] != 'L') continue;
        REP(d, 4) {
            ll nx = x + dx[d];
            ll ny = y + dy[d];
            if (inRange(nx, ny) && m[ny][nx] == 'C') {
                m[ny][nx] = 'W';
            }
        }
    }
    // REP(i, h) {
    //     cout << m[i] << endl;
    // }
    auto id = [&](ll x, ll y) { return y * w + x; };
    ll s = w*h, t = s+1;
    ll cnt = 0;
    REP(y, h) REP(x, w) if (m[y][x] == 'C') ++cnt;
    MaxFlow mf(t+1);
    REP(y, h) REP(x, w) {
        if (m[y][x] != 'C') continue;
        if ((x + y) % 2 != 0) continue;
        REP(d, 4) {
            ll nx = x + dx[d];
            ll ny = y + dy[d];
            if ( !inRange(nx, ny) ) continue;
            if (m[ny][nx] != 'C') continue;
            // cout << y << " " << x << " -> " << ny << " " << nx << endl;
            mf.add(id(x, y), id(nx, ny), 1);
        }
    }
    REP(y, h) REP(x, w) {
        if ((x + y) % 2 == 0) {
            mf.add(s, id(x, y), 1);
        }
        else {
            mf.add(id(x, y), t, 1);
        }
    }
    ll flow = mf.flow(s, t);
    // return 0;
    // cout << flow << endl;
    ll ans = cnt - flow;
    function<void(ll, ll)> fill = [&](ll x, ll y) {
        m[y][x] = 'X';
        REP(d, 4) {
            ll nx = x + dx[d];
            ll ny = y + dy[d];
            if (inRange(nx, ny) && m[ny][nx] == 'L') {
                fill(nx, ny);
            }
        }
    };
    REP(y, h) REP(x, w) {
        if (m[y][x] == 'L') {
            fill(x, y);
            ++ans;
        }
    }
    cout << ans << endl;
}