POJ-1222:EXTENDED LIGHTS OUT-翻转灭灯问题-非高斯消元

首先有一定的反转问题的基础基础反转
原题传送

题目大意

灭灯:有6 * 5的灯,一把下去可能关掉或点亮上下左右中5个灯,希望灭掉所有的灯,求灭灯方案。

灭灯思路

乍一看蛮复杂的,其实可以从第一行开始,先确定第一行的灭灯策略,然后从第二行开始分析,如果 ( i − 1 , j ) (i-1,j) (i1,j)位置是没关的,那么 ( i , j ) (i,j) (i,j)位置就必须关,只有他能影响到 ( i − 1 , j ) (i-1,j) (i1,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;
		}
	}
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值