题目:D - Swapping Puzzle (atcoder.jp)
样例输入:
4 5
1 2 3 4 5
6 7 8 9 10
11 12 13 14 15
16 17 18 19 20
1 3 2 5 4
11 13 12 15 14
6 8 7 10 9
16 18 17 20 19
样例输出:
3
题目翻译成人话:
给你n行m列的数组a和b, 让你对a数组任意交换任意两行或者任意两列,问你最少需要多少步,能让数组a等于数组b,不存在答案的情况下输出-1
正确的分析:
交换之后,我们关注的是结果,而不是你交换的哪一行,或者是哪一列? 这个时候就用到了全排列,对行用一次,对列用一次全排列。然后去check一下数组a是否等于数组b,要是相等的话,怎么去确定他们交换的次数呢? 逆序数!为什么是逆序数呢?
这个问题涉及到数学中的排列理论。首先,让我们来解释一下为什么是逆序数。
在一个由1到n组成的排列中,如果一个数比它后面的某个数大,那么它们就构成了一个逆序对。逆序数就是这样的逆序对的个数。
现在考虑将一个由1到n组成的排列打乱之后,我们需要通过交换操作将其变回有序状态。每次交换两个数时,它们之间的相对顺序会发生改变,因此交换次数与逆序数之间存在一种对应关系。
假设初始排列为a1, a2, ..., an,目标排列为1, 2, ..., n。我们从初始排列开始,每次选择一个位置进行交换,直到排列变为有序的目标排列。在进行交换的过程中,逆序对的数量会逐渐减小,最终变为0。
因此,初始排列到目标排列的交换次数就等于初始排列的逆序数,这就解释了为什么数组打乱后它们交换的次数是它们的逆序数。
这里的全排列直接可以用c++里面的 next_permutation()
正确的代码:
#include<bits/stdc++.h>
#define y1 Y1
#define fi first
#define endl "\n"
#define se second
#define PI acos(-1)
#define int long long
#define pb(x) push_back(x)
#define PII pair<int, int>
#define Yes cout << "Yes\n";
#define No cout << "No\n";
#define YES cout << "YES\n";
#define NO cout << "NO\n";
#define _for(i, a, b) for(int i = a; i <= b; ++i)
#define IOS ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
using namespace std;
const int N = 10;
int a[N][N], b[N][N];
int n, m, ret = 0x3f3f3f3f;
bool h[N][N], l[N][N];
int hp[N], lp[N];
bool check() {
_for(i, 1, n) {
_for(j, 1, m) {
if(a[hp[i]][lp[j]] != b[i][j]) {
return false;
}
}
}
return true;
}
int reverseNumber() {//逆序数
int t = 0;
for(int i = 1; i <= n; ++ i ) {
for(int j = 1; j <= i; ++ j) {
if(hp[i] < hp[j])t++;
}
}
for(int i = 1; i <= m; ++ i ) {
for(int j = 1; j <= i; ++ j ) {
if(lp[i] < lp[j])t++;
}
}
return t;
}
void dfsL() {
do{
if(check()) {
ret = min(ret, nxs());
}
}while(next_permutation(lp + 1, lp + m + 1));
}
void dfsH() {
do{
dfsL();
}while(next_permutation(hp + 1, hp + n + 1));
}
signed main() {
IOS;
cin >> n >> m;
_for(i, 1, n) {
_for(j, 1, m) {
cin >> a[i][j];
}
}
_for(i, 1, n) {
_for(j, 1, m) {
cin >> b[i][j];
}
}
_for(i, 1, max(n, m)) {
hp[i] = i;
lp[i] = i;
}
dfsH();
if(ret == 0x3f3f3f3f)ret = -1;
cout << ret << endl;
return 0;
}
正确的运行结果:
自己去实现全排列:
#include<bits/stdc++.h>
using namespace std;
const int N = 2e5 + 10;
int a[N], n;
bool st[N];
void dfs(int len) {
if(len == n + 1) {
for(int i = 1; i <= n; ++ i ) {
cout << a[i] << ' ';
}
cout << endl;
}
for(int i = 1; i <= n; ++ i ) {
if(!st[i]) {
a[len] = i;
st[i] = true;
dfs(len + 1);
st[i] = false;
}
}
}
signed main() {
cin >> n;
for(int i = 1; i <= n; ++ i) {
a[i] = i;
}
dfs(1);
return 0;
}
运行结果:
我之前的思路(错误的,但不知道错在哪了):
思路:
无脑交换呗,直接暴力枚举,交换,
代码:
#include<bits/stdc++.h>
#define y1 Y1
#define fi first
#define endl "\n"
#define se second
#define PI acos(-1)
#define int long long
#define pb(x) push_back(x)
#define PII pair<int, int>
#define Yes cout << "Yes\n";
#define No cout << "No\n";
#define YES cout << "YES\n";
#define NO cout << "NO\n";
#define _for(i, a, b) for(int i = a; i <= b; ++i)
#define IOS ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
using namespace std;
const int N = 10;
int a[N][N], b[N][N];
int n, m, ret = 0x3f3f3f3f;
bool h[N][N], l[N][N];
void swapH(int x, int y) {//交换的是行数 对应的列发生改变
for(int j = 1; j <= m; ++ j ) {
swap(a[x][j], a[y][j]);
}
}
void swapL(int x, int y) {//交换的是列数 对应的行发生改变
for(int i = 1; i <= n; ++ i ) {
swap(a[i][x], a[i][y]);
}
}
bool check() {
_for(i, 1, n) {
_for(j, 1, m) {
if(a[i][j] != b[i][j]) {
return false;
}
}
}
return true;
}
//暴搜是不行的 怎么去剪枝呢
void dfsL(int len, int sum) {//枚举的是交换的列数
if(sum >= ret)return;
if(len == m) {
if(check()) {
ret = min(ret, sum);
}
return;
}
for(int i = 1 + len; i <= m; ++ i ) {
dfsL(len + 1, sum);//不换
swapL(len, i);//交换列
dfsL(len + 1, sum + 1);//换
swapL(len, i);//回溯
}
}
void dfsH(int len, int sum) {// 枚举的是交换的行数
if(sum >= ret)return;
if(len == n) {
dfsL(1, sum);
return ;
}
for(int i = 1 + len; i <= n; ++ i ) {
dfsH(len + 1, sum); // 不换
swapH(len, i); //交换行
dfsH(len + 1, sum + 1); // 换
swapH(len, i);// 回溯
}
}
signed main() {
IOS;
cin >> n >> m;
_for(i, 1, n) {
_for(j, 1, m) {
cin >> a[i][j];
}
}
_for(i, 1, n) {
_for(j, 1, m) {
cin >> b[i][j];
}
}
dfsH(1, 0);
if(ret == 0x3f3f3f3f)ret = -1;
cout << ret << endl;
return 0;
}