天下一プログラマーコンテスト2015予選A C - 天下一美術館
問題概要
マスの状態を一致させるために、必要な最小コストを求める問題。
解法
はじめは、貪欲に、左上から見ていき、交換して変えて、一致するところを優先的にやればいけると思い書いたが、WA。原因は単純で、左上から貪欲にやるだけではだめで、どこから交換していくかで、変更できる枚数が変わってしまうためである。
解法は2部マッチングの最大値問題として解くことである。
黒白黒
白黒白
黒白黒
上記のようにマスを黒白と考えて、黒のマスから、周りの4方向のなかで、マスを交換するともう一方のモザイクアートと色が一致するようになるマスであれば、その黒のマスから周りの白のマスへ辺を張ることにする。それをすべての黒のマスについて行う。
この2部グラフにsとtを追加して、s -> 黒 -> 白 -> t の最大フロー問題として解く。
今回の問題が、2部マッチングの最大値を求める問題として解くことができる理由は、おなじマスを2度交換に使うようなことはないからである。一度交換されたマスは、もう一方のモザイクアートと一致してるため、2度交換するようなことは起きない。
最後に答えは、初めの状態で異なっているマスで交換もできなかったマスは、そのマス単体で交換しなければならないことになる。それを考えてコストを計算すればいい。
類題
ミス
すぐ2部マッチングだと気が付きたい。
コード
#include <iostream> #include <queue> #include <vector> #include <cstdio> #include <cstring> #include <algorithm> using namespace std; typedef long long ll; #define rep(i,n) for(int i=0;i<(n);i++) const int INF = 1e9; int w, h; int dy[] = {1, 0, -1, 0}; int dx[] = {0, 1, 0, -1}; int b1[100][100], b2[100][100]; const int MAX_V = 10000;//頂点数 struct Flow{ struct edge{ int to, cap, rev; }; vector<edge> G[MAX_V];//隣接リスト bool used[MAX_V]; void add_edge(int from, int to, int cap){ G[from].push_back((edge){to, cap, (int)G[to].size()});//from -> to G[to].push_back((edge){from, 0, (int)G[from].size() - 1});//to -> from } //増加パスを探す int dfs(int v, int t, int f){ if(v == t) return f; used[v] = true; for (int i = 0; i < G[v].size(); ++i){ edge &e = G[v][i]; if(!used[e.to] && e.cap > 0){ int d = dfs(e.to, t, min(f, e.cap)); if(d > 0){ e.cap -= d; G[e.to][e.rev].cap += d; return d; } } } return 0; } //sからtへの最大流 int max_flow(int s, int t){ int flow = 0; while(1){ memset(used, 0, sizeof(used)); int f = dfs(s, t, INF); if(f == 0) return flow; flow += f; } } }; int main(void){ cin >> h >> w; rep(i, h)rep(j, w) cin >> b1[i][j]; rep(i, h)rep(j, w) cin >> b2[i][j]; Flow flow; //s(h * w) -> x(i * w + j) for (int i = 0; i < h; ++i) { for (int j = 0; j < w; ++j) { if(i % 2 == 0){ if(j % 2 == 0) flow.add_edge(h * w, i * w + j, 1); }else{ if(j % 2 == 1) flow.add_edge(h * w, i * w + j, 1); } } } //y(i * w + j) -> t(h * w + 1) for (int i = 0; i < h; ++i) { for (int j = 0; j < w; ++j) { if(i % 2 == 0){ if(j % 2 == 1) flow.add_edge(i * w + j, h * w + 1, 1); }else{ if(j % 2 == 0) flow.add_edge(i * w + j, h * w + 1, 1); } } } //x -> y for (int i = 0; i < h; ++i) { for (int j = 0; j < w; ++j) { if(b1[i][j] != b2[i][j]){ if(i % 2 == 0){ if(j % 2 == 0){ rep(k, 4){ int ny = i + dy[k], nx = j + dx[k]; if(!(0 <= ny && ny < h && 0 <= nx && nx < w)) continue; if (b1[i][j] == b2[ny][nx] && b2[i][j] == b1[ny][nx]) { int num1 = w * i + j; int num2 = w * ny + nx; flow.add_edge(num1, num2, 1); } } } }else{ if(j % 2 == 1){ rep(k, 4){ int ny = i + dy[k], nx = j + dx[k]; if(!(0 <= ny && ny < h && 0 <= nx && nx < w)) continue; if (b1[i][j] == b2[ny][nx] && b2[i][j] == b1[ny][nx]) { int num1 = w * i + j; int num2 = w * ny + nx; flow.add_edge(num1, num2, 1); } } } } } } } int ans = 0; rep(i, h)rep(j, w){ if(b1[i][j] != b2[i][j]){ ans++; } } int ret = flow.max_flow(h * w, h * w + 1); // printf("ret %d ans %d\n", ret, ans); printf("%d\n", ret + ans - ret * 2); return 0; }