【solution】【位运算/枚举】AcWing 116. / POJ2965 The Pilots Brothers’ refrigerator

题目链接(AcWing)
题目链接(POJ)

statement

给定一个 4 × 4 4\times 4 4×4 的矩阵,由 +- 构成,每次各异选择一个位置 ( i , j ) (i,j) (i,j) ,然后将该点所在的行和列的状态调整( + → − , − → + +\to-,-\to+ +,+) ,问将整个矩阵都调整为 - 需要多少步。同时按照字典序输出方案,要求方案的字典序最小。


solution

首先,我们可以把这个矩阵从 ( 0 , 0 ) (0,0) (0,0) ( 3 , 3 ) (3,3) (3,3) 的每个位置从左到右,从上到下编号为 0 ∼ 15 0\sim15 015,即
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 \begin{matrix}0&1&2&3\\4&5&6&7\\8&9&10&11\\12&13&14&15\end{matrix} 0481215913261014371115

这个东西我们用一个 16 16 16 位二进制数 state 表示。对于 state 的第 k k k 位,若 k k k 对应在矩阵中的位置是 - 那么第 k k k 位就是 0 0 0 ;否则就是 1 1 1
state 可以在读入时处理出来。具体方法是对于 ( i , j ) (i,j) (i,j) ,对应的编号为 偏移量+列数 ,即 4 × i + j 4\times i+j 4×i+j i , j i,j i,j 都是从 0 0 0 开始的)。

int get(int x, int y) {return x * 4 + y;}

for (int i = 0; i < 4; i++)
{
	string line; cin >> line;
	for (int j = 0; j < 4; j++)
		if (line[j] == '+') state += 1 << get(i, j);
}

然后我们可以再搞一个 16 位二进制数 w w w 表示操作方案,这个 w w w 的第 k k k 位表示是否对编号为 k k k 的地方操作。于是我们只需要枚举这个 w w w ,然后每次枚举 w w w 的所有位,进行操作,看看最后的 state 是否为 0 0 0 就好。

于是现在重大的问题就是说,我们怎样进行操作。首先当然可以直接暴力搞这个十字形,但是 yxc 老师提供了一个非常巧妙的办法:我们只需要对矩阵的每个位置 ( i , j ) (i,j) (i,j) 再记录一个 16 16 16 位二进制数,表示那些位置需要被异或(把一个位置的状态改为其相反的,就是 C++ 中的异或运算)。假设这个二进制数是 a d d i , j add_{i,j} addi,j ,那么我们现在就可以得到若操作 ( i , j ) (i,j) (i,j) ,则需要把 s t a t e  xor  a d d i , j state\ \text{xor}\ add_{i,j} state xor addi,j ,这样就巧妙的实现了这个操作。

最后,遍历 w w w 的每一位 k k k 时,如何得到其在矩阵中的位置 x , y x,y x,y 也是个问题。我们看到 get() 这个函数:

int get(int x, int y) {return x * 4 + y;}

于是答案就呼之欲出了:x = k/4, y=k%4

这个题就做完了。

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <string> 
#include <vector>

using namespace std;

int get(int x, int y) {return x * 4 + y;}

int state;

int main()
{
	int add[15][15] = {};
	for (int i = 0; i < 4; i++)
	{
		string line; cin >> line;
		for (int j = 0; j < 4; j++)
			if (line[j] == '+') state += 1 << get(i, j);
	}
	for (int i = 0; i < 4; i++)
		for (int j = 0; j < 4; j++)
		{
			for (int k = 0; k < 4; k++)
			{
				add[i][j] += 1 << get(i, k);
				add[i][j] += 1 << get(k, j);
			}
			add[i][j] -= 1 << get(i, j);  // 十字形的中间被加了两次
		}
	vector<pair<int, int> > ans;
	for (int i = 0; i < 1 << 16; i++)
	{
		vector<pair<int, int> > res;
		int tmp = state;
		for (int j = 0; j < 16; j++)
			if (i >> j & 1)
			{
				int x = j / 4, y = j % 4;
				tmp ^= add[x][y];
				res.push_back(make_pair(x, y));
			}
		if (!tmp && (ans.empty() || ans.size() > res.size())) ans = res;
	}
	cout << ans.size() << "\n";
	for (int i = 0; i < ans.size(); i++)
		cout << ans[i].first + 1 << " " << ans[i].second + 1 << "\n";
	return 0;
}

总结

本题考察了位运算枚举的技巧,利用位运算的性质和其独特的表示方法可以简化许多问题,从而降低编程复杂度和时间复杂度。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值