1. 程式人生 > >POJ3279 Fliptile(DFS翻轉經典問題)

POJ3279 Fliptile(DFS翻轉經典問題)

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

 

[kuangbin帶你飛]專題一 簡單搜尋D - Fliptile(POJ 3279)

 

題意:

給一個N行M列的矩陣,值分別為0和1,每次你可以選擇將一個變成相反狀態,同時,它周圍的四個數也會變為相反狀態。 
問:最少翻轉多少次,可以將所有值都變成0 
多個解,輸出翻轉次數最少的(若有次數相同解,輸出字典序小的) 
若無解,輸出”IMPOSSIBLE”

 

解題思路:

對於每個點,只能有兩種操作,翻或不翻,若暴力所有可能性,需要2^(M*N)次操作,顯然不可行 
所以有了這個法子。 
先列舉第一行的所有可能性(2^M),搜尋或位運算均可 
然後,對座標(i, j)來說,如果(i-1, j)不為0,那麼(i, j)必然需要翻轉。 
重複進行上操作由2至N 
此時,最後一行也已翻轉完畢,如果最後一行全為0,得出結果 
第一行的所有結果中取最小值
 

 

有點繞。

為了求第二行到最後一行的翻轉狀態我們需要確定前一行的狀態。我們列舉第一行所有的情況,每列舉一種情況(第一行在此次列舉中不變了)就搜尋一下所有可能的結果,不斷更新狀態。

 

AC程式碼:

#include<cstdio>
#include<cstring>

int n,m;
int mp[20][20];
int f[20][20]={};
int ans[20][20]={};
int minn=0x1f1ffff;

bool judge()//判斷最後一行是否全為0 
{
	for(int i=1;i<=m;i++)
	{
		int t=f[n][i]+f[n][i-1]+f[n][i+1]+f[n-1][i];
		if((mp[n][i]+t)&1) return false;
	}
	return true;
}
//k是當前行 
void dfs(int k,int num)
{
	if(num>minn) return;//剪枝 
	if(k>n)
	{
		if(judge()&&num<minn)//判斷是否符合條件 
		{
			memcpy(ans,f,sizeof(f));//記錄當前最優解 
			minn=num;//更新翻轉最小次數 
		}
		return;
	}
	int t=0;
	for(int i=1;i<=m;i++)
	{ 
		if((mp[k-1][i]+f[k-2][i]+f[k-1][i-1]+f[k-1][i+1]+f[k-1][i])&1)//上一行mp[k-1][i]是否為1 即是否需要翻轉
		{
			f[k][i]=1;
			t++;
		}
		else f[k][i]=0;
	}
	dfs(k+1,num+t);
}

//k當前列 num第一行翻轉的次數
void todfs(int k,int num)
{
	if(k>m)
	{
		dfs(2,num);//對第一行每種情況進行搜尋 
		return;
	}
	f[1][k]=0;//不翻轉 
	todfs(k+1,num);
	f[1][k]=1;//翻轉 num+1 
	todfs(k+1,num+1);
}

int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)//下標從1開始便於邊界處理 
	{
		for(int j=1;j<=m;j++) 
		{
			scanf("%d",&mp[i][j]);
		}
	}
	todfs(1,0);//遞迴遍歷第一行所有情況 
	if(minn==0x1f1ffff) printf("IMPOSSIBLE\n");
	else
	{
		for(int i=1;i<=n;i++)
		{
			for(int j=1;j<=m;j++)
			{
				printf("%d ",ans[i][j]);
			}
			printf("\n");
		}
	}
	return 0;
}