写在前面:本人蒟蒻,写此分享来督促自己复习,并与大家分享有关开关问题的一些技巧以及例题代码
Fliptile题目链接
参考题解
翻转问题的主要技巧以及规律
详细内容见代码及注释
代码
//先用状态压缩的方法枚举第一行的翻转情况,然后通过第一行的情况来递推出第二行的情况
//即:已知了第一行的情况了,需要通过第二行来修正第一行的情况
//(因为第一行已经被枚举固定了,所以除了第二行之外,其他行无法修改第一行)
//以此类推:其他所有行的情况都可以被递推出(即:是已经固定的了)
//注意:除了最后一行,因为没有最后一行没有下一行来帮最后一行修正
//(所以最后一行也是固定的,无法修正,最后应该检查一下最后一行是否符合题意即可)
//关键是:由下一行来处理上一行(而且是垂直的上一行,因为只有垂直的上一行是固定的)
# include<iostream>
# include<algorithm>
# include<cstring>
using namespace std;
const int N = 20;
const int INF = 0x3f3f3f3f;
int n, m;
int a[N][N]; //原始数组
int b[N][N]; //保存翻转次数的数组
int c[N][N]; //记录答案的数组
int dx[] = {0, 1, 0, -1, 0}, dy[] = {0, 0, -1, 0, 1}; //其实棋子并不受下方棋子的影响
bool out(int x, int y)
{
if(x < 0 || x >= n|| y < 0 || y >= m) return true;
else return false;
}
int get_color(int x, int y)
{
int color = a[x][y]; //初始颜色(这里是a)
for(int i=0; i<5; ++i)
{
int nx = x + dx[i], ny = y + dy[i];
if(out(nx, ny)) continue;
color += b[nx][ny]; //这里是b
}
return color & 1; //本层翻转后的颜色(是固定的)
//翻转后的颜色等于初始颜色+翻转影响产生的颜色(上,下,左,右,中五个方向)
}
int flip(int s)
{
for(int i=1; i<=m; ++i) b[0][i-1] = (s>>(m-i)) & 1;
for(int i=1; i<n; ++i)
{
for(int j=0; j<m; ++j)
{
if(get_color(i-1, j)) b[i][j] = 1;
//由上一行的状态来递推出下一行的状态,因为上一行的上,左,右状态已经全部已知
//而正是由上方的状态来递推出其下方的状态
}
}
for(int i=0; i<m; ++i) if(get_color(n-1, i)) return INF; //检查最后一层
int times = 0;
for(int i=0; i<n; ++i) for(int j=0; j<m; ++j) times += b[i][j]; //得出最小翻转次数
return times;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0), cout.tie(0);
cin>>n>>m;
for(int i=0; i<n; ++i) for(int j=0; j<m; ++j) cin>>a[i][j];
int ans = INF;
//枚举的是第一行所有可能的翻转动作(即:翻(1)或不翻(0))
for(int i=0; i < (1<<m); ++i) //状态压缩,枚举第一行所有可能的情况 ,后面的只需递推了
{
memset(b, 0, sizeof b); //记得初始化b
int t = flip(i);
if(t < ans)
{
ans = t;
memcpy(c, b, sizeof b); //复制状态
}
}
if(ans == INF) cout<<"IMPOSSIBLE"<<endl;
else
{
for(int i=0; i<n; ++i)
{
for(int j=0; j<m; ++j)
{
cout<<c[i][j]<<' ';
}
cout<<endl;
}
}
return 0;
}