poj 2965

做完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很类似,应该也有唯一解。

想要找一个直接做就必中的方法,算式写了几页,决定先不写了。。。

花了一周时间总结这俩题!以后还是要速度一点。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值