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
0∼15,即
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;
}
总结
本题考察了位运算枚举的技巧,利用位运算的性质和其独特的表示方法可以简化许多问题,从而降低编程复杂度和时间复杂度。