【代碼超詳解】POJ 3279 Fliptile(開關問題,DFS)

一、題目描述

Fliptile

Time Limit: 2000MS Memory Limit: 65536K
Total Submissions: 25691 Accepted: 9099

Description

Farmer John knows that an intellectually satisfied cow is a happy cow who will give more milk. He has arranged a brainy activity for cows in which they manipulate an M × N grid (1 ≤ M ≤ 15; 1 ≤ N ≤ 15) of square tiles, each of which is colored black on one side and white on the other side.

As one would guess, when a single white tile is flipped, it changes to black; when a single black tile is flipped, it changes to white. The cows are rewarded when they flip the tiles so that each tile has the white side face up. However, the cows have rather large hooves and when they try to flip a certain tile, they also flip all the adjacent tiles (tiles that share a full edge with the flipped tile). Since the flips are tiring, the cows want to minimize the number of flips they have to make.

Help the cows determine the minimum number of flips required, and the locations to flip to achieve that minimum. If there are multiple ways to achieve the task with the minimum amount of flips, return the one with the least lexicographical ordering in the output when considered as a string. If the task is impossible, print one line with the word “IMPOSSIBLE”.

Input

Line 1: Two space-separated integers: M and N
Lines 2…M+1: Line i+1 describes the colors (left to right) of row i of the grid with N space-separated integers which are 1 for black and 0 for white

Output

Lines 1…M: Each line contains N space-separated integers, each specifying how many times to flip that particular location.

Sample Input

4 4
1 0 0 1
0 1 1 0
0 1 1 0
1 0 0 1

Sample Output

0 0 0 0
1 0 0 1
1 0 0 1
0 0 0 0

Source

USACO 2007 Open Silver

二、算法分析說明與代碼編寫指導

本指導是在 CSDN 的某篇博客的基礎上補充而來的(原博已找不到)。
題目大意是,讓奶牛翻轉(flip) M × N 的磚塊矩陣,使得每一個磚塊都變成白色。磚塊有兩面,分別是黑(1)白(0)兩色。因爲牛蹄太大,翻轉一個磚塊會令上下左右四個磚塊一起被翻轉。
要求輸出翻轉次數最小的方案。輸出的方案中,1 代表翻轉,0 代表不翻轉。
由於 M、N ≤ 15,數據量足夠小,可以進行 DFS 暴力枚舉。
首先我們僅需枚舉第一行的翻轉情況,剩餘行是否需要翻轉可以遞推出來:
AC 代碼的第 32、33、34、35 行

	I = 1 << n;
	for (int i = 0; i < I; ++i) {
		for (int j = 1; j <= m; ++j)fill(f[j] + 1, f[j] + n + 1, 0);
		for (int j = 0; j < n; ++j)f[1][n - j] = i >> j & 1;

是這樣的含義:i 可以當作一個長爲 n 的二進制序列。每一次枚舉,這個序列都會被從低位開始依次輸入到第一行。0 代表該格不翻轉,1 代表翻轉。
枚舉第一行的情形以後,就開始統計剩餘的行到底哪些格也需要翻轉。注意,現在只有第一行進行了翻轉,其餘行都沒有動。
flip_count() 函數用於完成剩餘行的翻轉,並統計每一行的翻轉次數。
從第二行開始逐個磚塊考察。如果某個磚塊的正上方的磚塊是黑色的,那麼這個磚塊必須要被翻轉(f[i][j] 記爲 1)。否則上面的磚塊會一直持續爲黑色,顯然不符合題目要求。翻轉這個磚塊會帶動周圍四個磚塊一起翻轉,不過無需對 f 作額外標記,因爲周圍磚塊最後到底被翻轉成了什麼顏色,都可以在後續的考察中被統計出來。
actual_color() 函數用於考察指定磚塊的顏色。c = t[x][y] 代表磚塊本來的顏色。統計完它本身還有周圍 4 格的翻轉次數,就可以算出這一格當前的顏色。
完成翻轉後,考察最後一行是否全白。如果不是,自然是不符合要求的。如果是全白,那麼意味着整個矩陣都是白色。
這可以用反證法證明:假設最後一行是全白,但是整個磚塊矩陣不是全白。如果黑磚塊在倒數第二行,那麼在剛纔嘗試翻轉最後一行時,這個黑磚塊會被考察到並翻轉爲白色。如果黑磚塊在倒數第三行,那麼在剛纔嘗試翻轉倒數第二行時,這個黑磚塊也會被考察到並翻轉爲白色。以此類推,可見無論黑磚塊在哪一行,都與剛纔已經完成的操作(如果某個磚塊的正上方是黑色,那麼翻轉這個磚塊,於是上面的黑色磚塊連帶被翻轉成白色)矛盾。所以假設不成立,即如果最後一行都是白色,那麼整個矩陣只能是白色。
如果這個解符合要求,那麼就要開始數到底翻轉了多少次了。每一個 f[i][j] = 1 的格都代表在這個格有一次翻轉。每個格最多隻進行一次翻轉或不翻轉,因爲翻轉兩次、三次分別跟不翻轉、翻轉一次是等效的。翻轉更多次數亦是如此。
比較當前方案的翻轉次數 c。如果小於已經記錄到的最少次數 c0,那麼這個新方案要被拷貝到 a 中,以備輸出。

三、AC 代碼

#include<cstdio>
#include<algorithm>
#pragma warning(disable:4996)
using namespace std;
int c0 = 1 << 30, c, m, n, I, t[17][17], f[17][17], a[17][17];
const int dx[] = { -1,0,0,0,1 }, dy[] = { 0,-1,0,1,0 };
inline int actual_color(const int& x, const int& y) {
	int c = t[x][y], X, Y;
	for (int i = 0; i < 5; ++i) {
		X = x + dx[i]; Y = y + dy[i];
		if (X > 0 && X <= m && Y > 0 && Y <= n)c += f[X][Y];
	}
	return c % 2;
}
inline int flip_count() {
	for (int i = 2; i <= m; ++i)
		for (int j = 1; j <= n; ++j)
			if (actual_color(i - 1, j))f[i][j] = 1;
	for (int j = 1; j <= n; ++j)if (actual_color(m, j)) { return 1 << 30; }
	int r = 0;
	for (int i = 1; i <= m; ++i)
		for (int j = 1; j <= n; ++j)
			r += f[i][j];
	return r;
}
int main() {
	scanf("%d%d", &m, &n); getchar();
	for (int i = 1; i <= m; ++i)
		for (int j = 1; j <= n; ++j) {
			t[i][j] = getchar() - '0'; getchar();
		}
	I = 1 << n;
	for (int i = 0; i < I; ++i) {
		for (int j = 1; j <= m; ++j)fill(f[j] + 1, f[j] + n + 1, 0);
		for (int j = 0; j < n; ++j)f[1][n - j] = i >> j & 1;
		c = flip_count();
		if (c < c0) {
			c0 = c;
			for (int i = 1; i <= m; ++i)copy(f[i] + 1, f[i] + n + 1, a[i] + 1);
		}
	}
	if (c0 == 1 << 30) { puts("IMPOSSIBLE"); return 0; }
	for (int i = 1; i <= m; ++i)
		for (int j = 1; j <= n; ++j)
			printf("%d%c", a[i][j], j == n ? '\n' : ' ');
	return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章