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
制約
入力例
5 4 LLWL CCCC CCCC CCCC LWLL
出力例
8
考察
- すでにわかっているLの周り(4近傍)のCはWが最適
- 答え = そのときの島の数 + 残りのCをいい感じに割り当てたときにできた島の数
- 市松模様で良さそうに見えるけど実はダメ
WCWWCW CCCCCC WCWWCW
- 残りのCから生まれたどの島も広さ1が最適
- つまり, 次の問題になった
残りのCは4近傍で辺を張ると, 二部グラフで, 隣り合わないようにいくつかの頂点を選び, 選んだ頂点数を最大化したい
- これは最大安定集合問題
- 二部グラフなのでフローで解ける
- Dinicでなのでとなり間に合う
解答
#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; }