做完2965这题,顿时明白了很久之前学长说的一句话,“如果每次写代码时,都思考怎样把代码改得更精简更有效率,你的编程能力一定能提高”。
题目是:4x4的格子,每个格子里画着‘-’或‘+’,可以对任意一个格子操作,操作一次的结果是这个格子所在行、列的格子里符号都会改变(加变减,减变加),想要把所有格子全变成‘-’,问至少操作几次。
4x4数据量可以承受直接枚举,然而稍不注意还是很容易超时的!我一开始想的是把操作的可能性枚举出来,就一个格子一个格子地在原4x4数组上进行操作,操作完之后检查是不是所有格子都是1(‘-’作为1,‘+’作为0)。现在原数组被改变了,所以还得开一个临时数组,每次操作之前把原数组复制到临时数组中去。似乎还可以统计一下行、列的操作次数。当一行的总操作数为奇数时,对这一行进行操作?好像不对!于是脑子混乱,卡在超时这一关走不下去了。
在同学提示下,才弄明白应该是“对每个格的操作等于这一行加这一列的操作数再加他自己”(最后还要模2)。(是了,应该一个点一个点的想,而不是一次想“一行”这样一口吃个胖子!)这个问题想清楚之后,发现还有个好处,统计完每行每列的操作数之后,就能得到每个格子的最终状态。每个格子算完之后,只需判断它是否是我想要的1,之后这个格子就再也用不到了,因此也不用保存了!这省去了每次枚举都要把原数组复制到临时数组这一大块时间,也是保证不超时的关键。
后来看讨论,发现有人这么做,对于每一个加号,只要把它所在行和列的所有格子都操作一次,就能把这个加号变成减号,并且不改变其他任何格子的状态。对每个加号都如此操作,就能得到想要的结果。这个构造很巧妙,但是它为什么是操作数最少的一个方案?
原因在于,如果按照操作规则列出线性方程组,16个方程16个未知数。发现这16个方程是线性不相关,系数矩阵为满秩矩阵,所以有唯一解。所以,只要找出一组解,就是“操作次数最少”的。现在大概明白为什么很多人用高斯消元法做了。
把线性方程组的东西也稍微记一下:
A是m*n矩阵,齐次线性方程组Ax=0的解存在且唯一(只有零解)的充分必要条件是 rank(A)=n,即A是列满秩的。
非齐次线性方程组Ax=b的解存在的充分必要条件是rank(A)=rank(A b)(增广矩阵)。当rank(A)=n时有唯一解,当rank(A)<n时有无穷多组解。
任选k行k列,构成A的k阶子矩阵,其行列式是A的k阶子式。A的不为零的子式的最大阶数称为A的秩。
A经初等变换后,秩不变。n维向量空间的n个线性无关向量排成矩阵A,则A一定是满秩的。
代码先写成这样,x%2与x&1等同,x/2与x>>1等同,以后应该记住。
#include <cstdio>
#include <iostream>
#include <cstring>
#include <cmath>
#include <climits>
#include <ctime>
#include <algorithm>
using namespace std;
int ori[4][4];
char board[4][5];
int min_switch[16];
int main() {
for (int i = 0; i < 4; ++i)
cin.getline(board[i], 5);
for (int i = 0; i < 4; ++i)
for (int j = 0; j < 4; ++j)
ori[i][j] = (board[i][j] == '-');
int switched[16] = {0};
int col[4] = {0};
int row[4] = {0};
int min_cnt = INT_MAX;
int n = 1 << 16;
for (int k = 0; k < n; ++k) {
int cnt = 0;
memset(switched, 0, sizeof(switched));
memset(col, 0, sizeof(col));
memset(row, 0, sizeof(row));
// if flip value of a position, record it in col[] and row[]
for (int bit = 0; bit < 16; ++bit) {
if (k & (1<<bit)) {
col[bit & 3] += 1;
row[bit >> 2] += 1;
cnt++;
switched[bit] = 1;
}
}
if (cnt >= min_cnt) continue;
bool valid = true;
// calculate the eventual value of each position
// meantime check if it is valid
for (int i = 0; i < 4; ++i) {
for (int j = 0; j < 4; ++j) {
// value of a position = (col+row+switched+ori)%2, and it should be 1.
if (!((col[j]+row[i]+switched[(i<<2)+j]+ori[i][j]) & 1) ) {
valid = false;
break;
}
}
if (!valid) break;
}
// update the min count and the switching strategy
if (valid && cnt < min_cnt) {
min_cnt = cnt;
for (int t = 0; t < 16; ++t) min_switch[t] = switched[t];
}
}
printf("%d\n", min_cnt);
for (int t = 0; t < 16; ++t) {
if (min_switch[t]) {
printf("%d %d\n", (t>>2)+1, (t&3)+1);
}
}
return 0;
}
1753题跟2965很类似,应该也有唯一解。
想要找一个直接做就必中的方法,算式写了几页,决定先不写了。。。
花了一周时间总结这俩题!以后还是要速度一点。