专栏文章
题解:P10456 The Pilots Brothers' refrigerator
P10456题解参与者 1已保存评论 0
文章操作
快速查看文章及其快照的属性,并进行相关操作。
- 当前评论
- 0 条
- 当前快照
- 1 份
- 快照标识符
- @mipq28io
- 此快照首次捕获于
- 2025/12/03 16:05 3 个月前
- 此快照最后确认于
- 2025/12/03 16:05 3 个月前
一种多项式时间算法
由于每个手柄有且只有 2 种状态,所以可以用二进制 0 和 1 来表示,因此可以将翻转问题建模为异或方程组(或者 上的线性方程组)。
异或方程组的构建
对于 4×4 矩阵的 16 个位置,我们定义 16 个变量 :
- : 表示翻转位置 。
- :表示不翻转位置 。
对于输入的矩阵,将符号 定义为 1(表示需要翻转为 ),符号 定义为 0(已经是目标状态)。初始矩阵的状态用 0 和 1 表示,例如:
CPP+--+ 1 0 0 1
-++- -> 0 1 1 0
+++- 1 1 1 0
-+++ 0 1 1 1
最终目标是使所有位置的最终状态变为 0。
现在考虑翻转操作的影响,显然,对于原矩阵中的每个状态,翻转两次相当于不翻转。翻转位置 [i,j] 会影响:
- 第 行的所有位置:, , , 。
- 第 列的所有位置:, , , 。
因此,我们可以将位置上的最终状态表示为初始状态加上翻转次数的异或:
因为目标是所有位置变为“-”(即 0),所以:
这就是位置 上的方程。
接下来就可以写出整个方程组的矩阵表示。总共有 16 个位置,因此需要 16 个方程,并且每个方程涉及 16 个变量 。
为方便处理,将 4×4 矩阵元素的坐标 映射为 0 到 15 的编号:
对于每个位置 :
- 第 行的变量 的系数为
- 第 列的 的系数为
- 常数项:
最终得到一个 16×17 的增广矩阵,如:
CPP位置 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 常数
[0,0] 1 1 1 1 1 0 0 0 1 0 0 0 1 0 0 0 | 1
[0,1] 1 1 1 1 0 1 0 0 0 1 0 0 0 1 0 0 | 0
[0,2] 1 1 1 1 0 0 1 0 0 0 1 0 0 0 1 0 | 0
[0,3] 1 1 1 1 0 0 0 1 0 0 0 1 0 0 0 1 | 1
...
表示为 ,其中 为系数矩阵, 为未知数列向量, 为常数项列向量
使用高斯消元法(在模 2 运算下)求解此矩阵方程即可。
有解性及唯一性证明
先考虑齐次方程 ,显然 是解。接下来讨论非平凡解是否存在。
显然, 不是解, 于是设某个 ,那么对于 且 , 位置 上的方程为 ,无解。于是对于第 列的所有 ,都有 。 同理,对于第 列的所有 ,都有 。重复这个过程,必然得到 ,于是 是唯一解。
由此可知, 有唯一解。
注意:这里“唯一”指的是翻转位置的集合唯一,而非翻转的顺序(顺序不影响异或结果)。
得到最简步骤
现在我们得到了一个解,但是这个解一定对应着最少翻转次数吗?由于方程组有唯一解,任何其他翻转组合要么无法实现目标,要么与此解相同。从而可知解中的每个操作都是必要的,去掉任何一个都会导致失败。
最优性证明
- 假设存在一个更小的操作集合 ,其翻转次数少于解向量中 1 的数量, 设 为 对应的向量。
- 若 , 则得到 ,与 中翻转次数少于解向量 中 1 的数量矛盾。
- 反之,则得到,与 是解矛盾。
于是解向量 便对应唯一的最优翻转操作步骤。 从 得到翻转操作是简单的。令 是 的第 个元素,由于使用映射 将 映射到整数,只需求出其反函数 即可知道翻转 对应的行和列。
复杂度 远优于二进制状态枚举
附上高斯消元代码
CPPint mat[M][M + 1];
vector<int> row_reduction() {
vector<int> solution(M, 0);
for(int col = 0; col < M; col++) {
int pivot = -1;
for(int row = col; row < M; row++) {
if(mat[row][col] == 1) {
pivot = row;
break;
}
}
if(pivot == -1) continue;
if(pivot != col)
for(int j = col; j <= M; j++)
swap(mat[col][j], mat[pivot][j]);
for(int row = 0; row < M; row++) {
if(row != col && mat[row][col] == 1)
for(int j = col; j <= M; j++)
mat[row][j] ^= mat[col][j];
}
}
for(int row = M - 1; row >= 0; row--) {
int leading = -1;
for(int col = 0; col < M; col++) {
if(mat[row][col] == 1) {
leading = col;
break;
}
}
if(leading == -1) continue;
solution[leading] = mat[row][M];
for(int col = leading + 1; col < M; col++)
if(mat[row][col] == 1)
solution[leading] ^= solution[col];
}
return solution;
}
相关推荐
评论
共 0 条评论,欢迎与作者交流。
正在加载评论...