题目大意
灭灯:有6 * 5的灯,一把下去可能关掉或点亮上下左右中5个灯,希望灭掉所有的灯,求灭灯方案。
灭灯思路
乍一看蛮复杂的,其实可以从第一行开始,先确定第一行的灭灯策略,然后从第二行开始分析,如果 ( i − 1 , j ) (i-1,j) (i−1,j)位置是没关的,那么 ( i , j ) (i,j) (i,j)位置就必须关,只有他能影响到 ( i − 1 , j ) (i-1,j) (i−1,j)。反复判断直到最后一行,最后一行如果能全关,那么更新答案,否则调整第一行的灭灯策略。下来按步骤进行分析
大致框架
使用 1 < < 6 1<<6 1<<6之间的每个数作为第一行的一次关灯策略,每次都要初始化 f l i p flip flip数组,然后根据 i i i将关灯的策略写进 f l i p flip flip,固定好第一行的关灯策略之后,开始 c n t cnt cnt该情况下全关需要的步数,保留步数最少的解。
for (ll i = 0; i < 5; i++)for (ll j = 0; j < 6; j++)cin >> arr[i][j];
for (ll i = 0; i < 1 << 6; i++) {//每个i化为二进制就是本次第一行的翻转方式
memset(flip, 0, sizeof(flip));
for (ll j = 0; j < 6; j++) {
flip[0][6 - j - 1] = i >> j & 1;//第一行的初始化,移位后和1与运算,从右到左进行填补
}
ll num = cnt(i);//以i为第一行的关灯策略,所需要的关灯次数
//这里我是找到需要开关次数最小的解。
if (num > -1 && (ans == -1 || num < ans)) {
ans = num;
memcpy(res, flip, sizeof(flip));
}
}
c n t cnt cnt函数
注意这里,我们并不需要翻转的时候遍历当前点上下左右去修改 a r r arr arr数组中每个元素是0还是1,只需要使用 g e t get get函数来判断 ( x , y ) (x,y) (x,y)位置是否需要修改,修改的话将 f l i p [ i ] [ j ] flip[i][j] flip[i][j]置一即可。
ll get(ll x, ll y) {
ll c = arr[x][y]; //统计x,y上下左右中的兄弟一共翻转了多少次,不要忘记算xy这个位置应不应该翻转
for (ll i = 0; i < 5; i++) {
ll xx = x + dx[i], yy = y + dy[i];
if (xx >= 0 && xx < 5 && yy >= 0 && yy < 6) {
c += flip[xx][yy];
}
}
return c % 2;
}
ll cnt(ll k) {
ll ans = 0;
//从第二行开始,第一行决定第二行怎么翻转,第二行决定第三行怎么翻转。
for (ll i = 1; i < 5; i++) {
for (ll j = 0; j < 6; j++) {
if (get(i - 1, j)) {//统计对(i-1,j)位置有影响的所有位置的翻转次数之和
flip[i][j] = 1;
}
}
}
//判断最后一行是否满足要求
for (ll i = 0; i < 6; i++) {
if (get(4, i) != 0) return -1;//4是最后一行
}
//统计翻转次数
for (ll i = 0; i < 5; i++) {
for (ll j = 0; j < 6; j++) {
ans += flip[i][j];
}
}
return ans;
}
完整代码
#include<iostream>
#include<iomanip>
#include<cmath>
#include<string.h>
#include<queue>
#include<vector>
#include<map>
#include<algorithm>
using namespace std;
#define Max 5005
#define ll long long
#define inf 1000005
#define p pair<ll,ll>
ll n, M = inf, ans = inf, K;
ll arr[5][6], flip[5][6], res[5][6];//初始值,某次实验i行j列是否翻转,结果
ll dx[5] = { 0,0,0,-1,1 }, dy[5] = { 0,-1,1,0,0 };
ll get(ll x, ll y) {
ll c = arr[x][y]; //统计x,y上下左右中的兄弟一共翻转了多少次,不要忘记算xy这个位置应不应该翻转
for (ll i = 0; i < 5; i++) {
ll xx = x + dx[i], yy = y + dy[i];
if (xx >= 0 && xx < 5 && yy >= 0 && yy < 6) {
c += flip[xx][yy];
}
}
return c % 2;
}
ll cnt(ll k) {
ll ans = 0;
//从第二行开始,第一行决定第二行怎么翻转,第二行决定第三行怎么翻转。
for (ll i = 1; i < 5; i++) {
for (ll j = 0; j < 6; j++) {
if (get(i - 1, j)) {//统计对(i-1,j)位置有影响的所有位置的翻转次数之和
flip[i][j] = 1;
}
}
}
//判断最后一行是否满足要求
for (ll i = 0; i < 6; i++) {
if (get(4, i) != 0) return -1;//4是最后一行
}
//统计翻转次数
for (ll i = 0; i < 5; i++) {
for (ll j = 0; j < 6; j++) {
ans += flip[i][j];
}
}
return ans;
}
int main() {
cin >> n;
for(ll k=1;k<=n;k++){
ll ans = -1;
for (ll i = 0; i < 5; i++)for (ll j = 0; j < 6; j++)cin >> arr[i][j];
for (ll i = 0; i < 1 << 6; i++) {//每个i化为二进制就是本次第一行的翻转方式
memset(flip, 0, sizeof(flip));
for (ll j = 0; j < 6; j++) {
flip[0][6 - j - 1] = i >> j & 1;//第一行的初始化,移位后和1与运算,从右到左进行填补
}
ll num = cnt(i);//以i为第一行的关灯策略,所需要的关灯次数
//这里我是找到需要开关次数最小的解。
if (num > -1 && (ans == -1 || num < ans)) {
ans = num;
memcpy(res, flip, sizeof(flip));
}
}
cout << "PUZZLE #" << k << endl;
for (ll i = 0; i < 5; i++) {
for (ll j = 0; j < 6; j++) {
cout << res[i][j];
if (j < 5) cout << " ";
}
cout << endl;
}
}
}