经典算法 金币阵列问题

金币阵列问题

题目描述

有m × n(m ≤ 100 ,n ≤ 100) 个金币在桌面上排成一个m 行n 列的金币阵列。每一枚金币或正面朝上或背面朝上。用数字表示金币状态,0 表示金币正面朝上,1 表示背面朝上。

金币阵列游戏的规则是:

(1)每次可将任一行金币翻过来放在原来的位置上;

(2)每次可任选2 列,交换这2 列金币的位置。

编程任务

给定金币阵列的初始状态和目标状态,编程计算按金币游戏规则,将金币阵列从初始状态变换到目标状态所需的最少变换次数。

数据输入:

输入有多组数据。第1 行有1 个正整数k,表示有k 组数据。每组数据的第1 行有2 个正整数m 和n。以下的m 行是金币阵列的初始状态,每行有n 个数字表示该行金币的状态,0 表示金币正面朝上,1 表示背面朝上。接着的m 行是金币阵列的目标状态。

结果输出:

将计算出的最少变换次数按照输入数据的次序输出。相应数据无解时输出-1。

示例输入

in 复制代码
4
4 3
1 0 1
0 0 0
1 1 0
1 0 1
1 0 1
1 1 1
0 1 1
1 0 1
4 3
1 0 1
0 0 0
1 0 0
1 1 1
1 1 0
1 1 1
0 1 1
1 0 1
4 3
0 0 1
1 1 1
0 1 0
1 1 0
1 0 1
1 1 1
0 1 1
1 0 1
3 4
0 1 0 0
1 1 0 0
0 1 1 0
0 1 0 0
0 1 0 1
0 1 1 0

示例输出

out 复制代码
2
-1
4
1

c++代码

cpp 复制代码
#include<bits/stdc++.h>

using namespace std;

int m, n, ans = INT_MAX, T;
vector<vector<int>> coins, goals, middle;
vector<string> goals_col, middle_col;

void change_row(int i) {
    for (int k = 0; k < n; k++) {
        if (middle[i][k] == 0) middle[i][k] = 1;
        else middle[i][k] = 0;
    }
}

void change_col(int i, int j) {
    for (int k = 0; k < m; k++) {
        swap(middle[k][i], middle[k][j]);
    }
    swap(middle_col[i], middle_col[j]);
}

int main() {
    cin >> T;
    while (T--) {
        ans = INT_MAX;
        cin >> m >> n;
        coins = vector<vector<int>>(m, vector<int>(n));
        goals = vector<vector<int>>(m, vector<int>(n));
        goals_col = vector<string>(n);
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                cin >> coins[i][j];
            }
        }
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                cin >> goals[i][j];
            }
        }
        for (int j = 0; j < n; j++) {
            for (int i = 0; i < m; i++) {
                goals_col[j] += goals[i][j] + '0';
            }
        }
        for (int i = 0; i < n; i++) {
            middle = coins;
            middle_col = vector<string>(n);
            bool key = true;
            int cont = 0;
            if (i != 0) {
                change_col(i, 0);
                cont++;
            }
            for (int j = 0; j < m; j++) {
                if (middle[j][0] != goals[j][0]) {
                    change_row(j);
                    cont++;
                }
            }
            for (int j = 0; j < n; j++) {
                for (int i = 0; i < m; i++) {
                    middle_col[j] += middle[i][j] + '0';
                }
            }
            for (int j = 1; j < n; j++) {
                if (middle_col[j] == goals_col[j]) continue;
                int x = -1;
                for (int k = j + 1; k < n; k++) {
                    if (middle_col[k] != goals_col[j]) continue;
                    if (x == -1) {
                        x = k;
                        continue;
                    }
                    if (middle_col[k] != goals_col[k]) {
                        x = k;
                        break;
                    }
                }
                if (x == -1) {
                    key = false;
                    break;
                }
                change_col(x, j);
                cont++;
            }
            if (key) ans = min(ans, cont);
        }
        if (ans == INT_MAX) ans = -1;
        cout << ans << endl;
    }
    return 0;
}//by wqs

问题解析

我认为这是一个贪心问题

首先行是不能交换的,只能翻转,列可以交换

所以从初始到目标状态,行的位置是不变的,我们只要看列的位置。

如果有n列,要么第一列本来就是对应目标第一列,要么第二列对应目标第一列,要么第三列对应目标第一列...要么第n列对应目标第一列,也有可能不止一种可能,但是我们要选择变换次数最少的一种。

确定第一列

我们干脆穷举1-n,设第n列就是对应目标第一列

cpp 复制代码
for (int i = 0; i < n; i++) {
   /...
    bool key = true;
    int cont = 0;
    if (i != 0) {
        change_col(i, 0);
        cont++;
    }
    /...
    if (key) ans = min(ans, cont);
}

这样第一列就这么确定了,第一列我们一行一行地看,如果每一行的第一个金币和目标金币状态不同,我们给这一整行翻转。

如果第一列的状态和目标状态不符合翻转一整行

cpp 复制代码
for (int i = 0; i < n; i++) {
   /...
    bool key = true;
    int cont = 0;
    if (i != 0) {
        change_col(i, 0);
        cont++;
    }
    for (int j = 0; j < m; j++) {
        if (middle[j][0] != goals[j][0]) {
            change_row(j);
            cont++;
        }
    }
    /...
    if (key) ans = min(ans, cont);
}

我们以后再也不能翻转行了,因为只要一翻转,第一列的数字就会变得不符合,接下来我们只需要考虑列的交换

交换列

后面还有n-1列,我们遍历这些列,我们的目的是使这一列和目标状态一致

假设遍历到了第j列

为了加快比较速度,我们利用字符串数组,存储列的信息

例如

1 0 1

0 0 0

1 1 0

1 0 1

转换为字符串数组为1011、0010、1001

vector middle_col表示middle的信息

vector goals_col表示goals的信息

如果第j列和目标一致直接下一列

cpp 复制代码
if (middle_col[j] == goals_col[j]) continue;

如果第j列和目标不一致

我们这个时候就要让第j列和第j列后面的某一列交换,所以我们往后找,有没有可以交换的。

最简单的情况,如果没有一列可以交换说明第一列出了问题,我们当时不应该让这个变成第一列,直接考虑让其他的变成第一列。

如果找到了,只找到了1个,直接交换就行。

问题是如果找到了1个以上,我们就要考虑利益了

如果我交换某一列,使得那一列原来和目标一致变得不一致的,我就不会选择它,因为我没必要破坏它,宁愿选择其他的。

如果交换某一列,那一列本来就和目标不一致,我就可以直接选择这一列,反正这一列也本来就要换,说不定我把我这一列换过去,那一列还刚好吻合了。

如果交换必定使得那一列原来和目标一致变得不一致,那没办法了,不交换我当前列就不一致,我只能交换。

所以我才说这是一个贪心的问题。

cpp 复制代码
int x = -1;//第j列和第x列交换
for (int k = j + 1; k < n; k++) {
    if (middle_col[k] != goals_col[j]) continue;
    if (x == -1) {
        x = k;
        continue;
    }
    if (middle_col[k] != goals_col[k]) {
        x = k;
        break;
    }
}
相关推荐
码熔burning1 小时前
(十 五)趣学设计模式 之 命令模式!
java·设计模式·命令模式
秋已杰爱1 小时前
Qt显示一个hello world
开发语言·qt
我不会编程5552 小时前
Python Cookbook-2.24 在 Mac OSX平台上统计PDF文档的页数
开发语言·python·pdf
胡歌13 小时前
final 关键字在不同上下文中的用法及其名称
开发语言·jvm·python
程序员张小厨4 小时前
【0005】Python变量详解
开发语言·python
计算机-秋大田4 小时前
基于Spring Boot的乡村养老服务管理系统设计与实现(LW+源码+讲解)
java·vue.js·spring boot·后端·课程设计
盖盖衍上4 小时前
Java 泛型(Generics)详解与使用
java·开发语言·windows
深蓝海拓5 小时前
PySide(PyQT)重新定义contextMenuEvent()实现鼠标右键弹出菜单
开发语言·python·pyqt
没有十八岁5 小时前
云创智城YunCharge 新能源二轮、四轮充电解决方案(云快充、万马爱充、中电联、OCPP1.6J等多个私有单车、汽车充电协议)之新能源充电行业系统说明书
java·数据库·spring·汽车
小萌新上大分6 小时前
Minio搭建并在SpringBoot中使用完成用户头像的上传
java·spring boot·后端·minio·minio搭建·头像上传·minio入门