题目链接
https://vjudge.net/problem/POJ-3279
拿到这道题想了很久,都没找到什么好的做饭,一看题解,好家伙,直接暴力是没想到的,一看数据最多才15行15列,之间暴力就OK了。
首先,暴力第一行的所有可能性,2的15次方,大概32768种可能性。
只要第一行确定下来了,之后的情况都可以之间根据上一行来锁定出下一行,然后确定出整个矩阵的最优解。
注意,同样踩踏次数的可能性下,要选择字典序最小的那种踩踏情况,所以要从最小的数0开始,一直确定到最大的数2的15次方。
AC代码
#include <iostream>
#include <cstring>
using namespace std;
int M, N;
const int MAX_M = 15;
const int MAX_N = 15;
int tile[MAX_M][MAX_N];
int opt[MAX_M][MAX_N]; // 保存最优解
int flip[MAX_M][MAX_N]; // 保存中间结果
const int dx[5] = {-1, 0, 0, 0, 1};
const int dy[5] = {0, -1, 0, 1, 0};
// 查询(x, y)的颜色
// 这里当时出现了一点小错误,因为默认把x看做横坐标,y看做纵坐标了,其实不是这样的,下次x和y容易用反的话,还是用i和j来表示更舒服一点。
int get(int x, int y)
{
int c = tile[x][y];
for (int d = 0; d < 5; ++d)
{
int x2 = x + dx[d];
int y2 = y + dy[d];
if (0 <= x2 && x2 < M && 0 <= y2 && y2 < N)
{
c += flip[x2][y2];
}
}
return c % 2;
}
// 求出第1行确定情况下的最小操作数
// 无解的情况返回-1
int calc()
{
for (int i = 1; i < M; ++i)
{
for (int j = 0; j < N; ++j)
{
// 如果(i - 1, j)是黑色的话,则必须反转这个格子
if(get(i - 1, j) != 0)
{
flip[i][j] = 1;
}
}
}
// 判断最后一行是否全为白色
for (int j = 0; j < N; ++j)
{
if (get(M - 1, j) != 0)
{
return -1;
}
}
// 统计翻转次数
int res = 0;
for (int i = 0; i < M; ++i)
{
for (int j = 0; j < N; ++j)
{
res += flip[i][j];
}
}
return res;
}
void solve()
{
int res = -1;
// 按照字典序尝试第一行所有的可能性
for (int i = 0; i < (1 << N); ++i)
{
memset(flip, 0, sizeof(flip));
for (int j = 0; j < N; ++j)
{
flip[0][N - j - 1] = i >> j & 1;
}
int num = calc();
if (num >= 0 && (res < 0 || res > num)) {
res = num;
memcpy(opt, flip, sizeof(flip));
}
}
// 输出结果
if (res < 0)
{
cout << "IMPOSSIBLE" << endl;
}
else
{
for (int i = 0; i < M; ++i)
{
for (int j = 0; j < N; ++j)
{
cout << opt[i][j];
if (j < N - 1)
{
cout << ' ';
}
else
{
cout << endl;
}
}
}
}
}
int main()
{
cin.tie(0);
cout.tie(0);
ios::sync_with_stdio(false);
cin >> M;
cin >> N;
for (int i = 0; i < M; ++i)
{
for (int j = 0; j < N; ++j)
{
cin >> tile[i][j];
}
}
solve();
return 0;
}