1. 程式人生 > 其它 >Codeforces 1592F2 Alice and Recoloring 2

Codeforces 1592F2 Alice and Recoloring 2

有一張 \(n \times m\) 的方格圖,初始全白,目標狀態給定。

有 4 種操作(反轉的意思是黑 \(\to\)白,白 \(\to\) 黑):

  • 反轉一個包含 \((1, 1)\) 的子矩形,花費 \(1\)
  • 反轉一個包含 \((n, 1)\) 的子矩形,花費 \(3\)
  • 反轉一個包含 \((1, m)\) 的子矩形,花費 \(4\)
  • 反轉一個包含 \((n, m)\) 的子矩形,花費 \(2\)

求達成目標狀態的最小花費。

\(1 \le n, m \le 500\)

2s, 256MB


引理 1:第 2 和第 3 種操作不會用到。

證明:考慮到不管是第 2 種還是第 3 種操作,都可以用兩次第 1 種操作代替,而且代價顯然更優。所以永遠不會使用第 2 和第 3 種操作。

現在的問題就轉化為了只存在第 1 和第 4 種操作的情況了。

將目標狀態與初始狀態對換,即給定一個初始有黑白兩色的方格圖,花最小的代價使得全部變成白色。

矩形反轉顯然是不好處理的,考慮弄一個類似字首和的東西來優化掉。

將黑色視為 \(1\),白色視為 \(0\)。構造一個數組 \(a\),其中 \(a_{i, j} = s_{i, j} \oplus s_{i + 1, j} \oplus s_{i, j + 1} \oplus s_{i + 1, j + 1}\)(超出網格的部分預設是白色)。

非常顯然,當 \(a\) 陣列全部變為 \(0\) 時,\(s\) 陣列也就全部變為了 \(0\)

觀察 1, 4 兩種操作對 \(a\) 陣列的影響,發現是:

  • 對於第 1 種操作,只會有 1 個格子的 \(a\) 發生了反轉。
  • 對於第 4 種操作,會有 4 個格子的 \(a\)發生反轉,且這 4 個格子形如 \((x, y)\)\((n, y)\)\((x, m)\)\((n, m)\)。記這樣的操作為 \(op(x, y)\)

引理 2:不會同時使用 \(op(x, y_1)\)\(op(x, y_2)\)。同理不會同時使用 \(op(x_1, y)\)\(op(x_2, y)\)

證明:以前一種為例,\((n, m)\)\((x, m)\) 都被反轉了兩次,所以不會發生改變。那麼也就是花費了 \(4\)

的代價來反轉了 \(2\times 2=4\) 個格子。這顯然可以被第 1 種操作代替。

引理 3:除非 \((x, y)\)\((n, y)\)\((x, m)\) 都為 \(1\),才會使用 \(op(x, y)\)

證明:如果這中間有一個不為 \(1\),那麼這次操作就產生了一個錯誤的反轉。為了達成最終目標,顯然會使用一次第 1 種操作把它反轉回來(注:不會是另一個第 4 種操作,根據引理 2 可以得知)。那麼僅僅是反轉了另外 3 個格子,代價都至少為 \(1 + 2 = 3\),完全可以使用第 1 種操作代替。

於是就可以做題了,建立一個二分圖,左部有 \(n - 1\) 個點代表行,右部有 \(m - 1\) 個點代表列。

對於 \((x, y)\),如果它滿足引理 3 的條件,則把左部的 \(x\) 和右部的 \(y\) 連邊。

求這個二分圖的最大匹配數 \(k\)即可。答案為 \(\textit{rem} - k\)\(\textit{rem}\)表示剩下的 \(1\) 的個數。

我使用的是 dinic 求二分圖最大匹配,時間複雜度為 \(O(n^2 \sqrt{n})\)

#include <bits/stdc++.h>
const int N = 505;
char str[N][N];
int n, m, cnt = -1, a[N][N], h[N << 1], S, T;

struct edge {
	int v, w, nxt;
} e[N * N * 2];

void add_edge(int u, int v) {
	e[++cnt] = (edge){v, 1, h[u]}; h[u] = cnt;
	e[++cnt] = (edge){u, 0, h[v]}; h[v] = cnt;
}

int que[N << 1], hd, tl, lev[N << 1];
 
bool bfs() {	 
	memset(lev, 0, sizeof(lev));
	hd = tl = lev[S] = 1; que[1] = S;
	while(hd <= tl) {
		int u = que[hd++];
		for(int i = h[u]; ~i; i = e[i].nxt) {
			int v = e[i].v;
			if(!lev[v] && e[i].w > 0) {
				que[++tl] = v;
				lev[v] = lev[u] + 1;
			}
		}
	}
	return lev[T];
}

int cur[N << 1];
 
int dfs(int u, int can_flow) {
	if(u == T) return can_flow;
	int res_flow = 0;
	for(int &i = cur[u]; ~i; i = e[i].nxt) {
		int v = e[i].v;
		if(lev[v] == lev[u] + 1 && e[i].w > 0) {
			int will_flow = dfs(v, std::min(can_flow, e[i].w));
			res_flow += will_flow;
			can_flow -= will_flow;
			e[i ^ 1].w += will_flow;
			e[i].w -= will_flow;
			if(!can_flow) break;
		}
	}
	if(!res_flow) lev[u] = 0;
	return res_flow;
}
 
int dinic() {
	int res = 0; 
	while(bfs()) {
		memcpy(cur, h, sizeof(h));
		res += dfs(S, INT_MAX);
	}
	return res;
}

int main() {
	memset(h, -1, sizeof(h));

	scanf("%d %d", &n, &m); 
	S = n + m + 1; T = n + m + 2;
	for(int i = 1; i <= n; i++) {
		scanf("%s", str[i] + 1);
	}
	for(int i = 1; i <= n; i++) {
		for(int j = 1; j <= m; j++) {
			a[i][j] = (str[i][j] == 'B') ^ (str[i][j + 1] == 'B') ^ (str[i + 1][j] == 'B') ^ (str[i + 1][j + 1] == 'B');
		}
	}
	for(int i = 1; i < n; i++) {
		for(int j = 1; j < m; j++) {
			if(a[i][j] && a[n][j] && a[i][m]) {
				add_edge(i, j + n);
			}
		}
	}
	for(int i = 1; i < n; i++) add_edge(S, i);
	for(int j = 1; j < m; j++) add_edge(j + n, T);

	int k = dinic(), ans = 0;
	
	a[n][m] ^= (k & 1);
	for(int i = 1; i <= n; i++) {
		for(int j = 1; j <= m; j++) {
			ans += a[i][j];
		}
	}
	ans -= k;
	printf("%d\n", ans);
	return 0;
}