題目描述
給你一個01矩陣,矩陣大小爲M x N。(1 <= M , N <= 15)
每次操作選擇一個格子,使得該格子與上下左右四個格子的值翻轉。
至少多少次操作可以使得矩陣中所有的值變爲0?
請輸出翻轉方案,若沒有方案,輸出"IMPOSSIBLE” 。
輸入格式
第一行輸入兩個數:M和N。(1 <= M , N <= 15)
接下來M行,每行N個數,其值只爲0或1。
輸出格式
輸出M行,每行N個數。
每個數代表該位置翻轉次數
樣例輸入
4 4
1 0 0 1
0 1 1 0
0 1 1 0
1 0 0 1
樣例輸出
0 0 0 0
1 0 0 1
1 0 0 1
0 0 0 0
題解:
- 題目數據較小,可以考慮用DFS搜索
- 簡單的模擬枚舉會TLE,考慮簡化一下:
- 如果從 按行往下 開始枚舉,則可以讓只有當前位置的上一行爲 1 時才翻轉當前位置
- 而對於第一行,則有種翻轉的可能,對此對於每一種可能進行搜索。最後根據最後一行是否全是0判斷成功與否。
- 對於此類兩面翻轉問題,判斷一個位置的屬性可以通過(被翻轉次數)% 2 得到。這樣就不需要在原數據上模擬翻轉了
- 在該問題中 被翻轉次數 = 當前位置的屬性(0/1) + 上下左右中 五個位置的翻轉次數
- 模擬第一行的情況可以使用二進制枚舉。
#include<cstdio>
#include<cstring>
#include<iostream>
//#define DEBUG
using namespace std;
int m, n;
int a[20][20], cnt[20][20], ans=0x3f3f3f3f, sumc = 0, anscnt[20][20];
int dir[5][2] = {{0,0},{0,1},{0,-1},{1,0},{-1,0}};
bool surpass(int i, int j){
if(i<0 || i>=m || j<0 || j>=n) return true;
return false;
}
int get(int i, int j)
{
int temp = a[i][j];
for(int k=0;k<5;k++)
{
int x = i+dir[k][0];
int y = j+dir[k][1];
if(!surpass(x, y)) temp += cnt[x][y];
}
return temp & 1;
}
void dfs()
{
for(int i=0;i<(1<<n);i++)
{
memset(cnt, 0, sizeof cnt);
sumc = 0; //改變的次數
int k = i;
for(int j=0;j<n;j++){
cnt[0][n-j-1] = k & 1;
sumc+= cnt[0][n-j-1];
k >>= 1;
}
for(int j=1;j<m;j++)
{
for(k=0;k<n;k++)
{
if(get(j-1, k)) //上方爲1
{
cnt[j][k]=1;
sumc++;
}
}
}
int sumz = 0;
for(int j=0;j<n;j++)
{
sumz += get(m-1,j); // 統計最後一行的0
}
if(sumz==0 && sumc < ans)
{
ans = sumc;
memcpy(anscnt, cnt, sizeof cnt);
}
}
}
int main()
{
scanf("%d %d", &m, &n);
int sum=0;
for(int i=0;i<m;i++)
{
for(int j=0;j<n;j++){
scanf("%d", &a[i][j]);
sum+=a[i][j];
}
}
memset(cnt, 0, sizeof cnt);
dfs();
if(ans == 0x3f3f3f3f) cout << "IMPOSSIBLE" << endl;
else
for(int i=0;i<m;i++)
{
for(int j=0;j<n;j++)
printf("%d%c", anscnt[i][j], (j==n-1)?'\n':' ');
}
return 0;
}