原题:https://codeforces.com/problemset/problem/1980/E

题目大意:给你一个 n x m 的矩阵,包含1到n*m的整数排列

n 个整数的排列是一个数组,包含从 1 到的所有整数。
例如,数组 [1], [2,1,3], [5,4,3,2,1] 是排列
而数组 [1,1], [100], [1,2,4,5] 不是排列

你可以对这个矩阵进行无限次操作的 行变换 或者 列变换。
给你原始矩阵 a 和矩阵 b 。你的任务是确定是否可以通过给定的运算将矩阵 a 变换为矩阵 b

解析

首先要知道这么一个数学的事实:对于一个矩阵,在单独一次操作中,行变换并不会改变列,而列变换也不会改变行。

我们选取矩阵中某个元素为e,然后选取该元素所在的行与列,我们把行内的元素叫做排列R,而列内的元素叫做C。

当对这个矩阵任意次行列变换后:
R内所有元素的行编号仍相等,因为列变换不会改变行
C内所有元素的列编号仍相等,因为行变换不会改变列

还注意到:行列变换会打破排列之间的顺序,但是并不会改变排列元素的集合

由此便可以这样思考问题,矩阵a可以通过行列变换变成矩阵b的条件是:对于某个元素,我们对矩阵进行任意次的行列变换后,这个元素在a矩阵的 排序后的行列排列 与这个元素在b矩阵的 排序后行列排列 仍然相等。

开两个pair数组pa, pb,分别记录数字在a矩阵和b矩阵中的位置。遍历 1~n*m 寻找每个数字它在a矩阵和b矩阵中的排序后的行列排列是否相等。

时间复杂度

比较容易想到的排序方法是 开 vector<vector<int>> sax, say, sbx, sby 四个数组记录已经排好的序列,然后在读入矩阵后排序一次。

sax[i] 表示在a中取第i行的排列 (已排序)
say[i] 表示在a中取第i列的排列(已排序)

然后对于每个数字的位置,遍历检查 sax 和 sbx, say 与 sby 是否相等

然而,使用这种办法在遍历 1~n*\m 个数字的时候,最坏的总体时间复杂度是会达到 **O(n^2 * m^2) **的

想办法优化时间复杂度,对行列排列进行散列化是一个可能的方案

散列化

我们的目标是通过散列化把比较排列的时间复杂度降到O(1),那么必须用某个哈希值代替掉vector<vector<int>> 中的 vector<int>

由于C++并没有vector哈希函数的模版,因此需要手动对行列进行散列操作,其中一种容易想到的操作是把排序后的行列排列变为字符串,再用string哈希进行散列操作

最后,我们遍历 1~n*\m 个数字,检查两个矩阵里面这个数字所在排列的哈希值是否相同即可。至此,时间复杂度为 O(nm)

代码

#include <bits/stdc++.h> 
using namespace std;

const int mod = 2e5+10;
typedef long long ll;
#define x first
#define y second
typedef pair<int, int> P;

vector<vector<int>> a, b;
vector<P> pa(mod), pb(mod);

//sax[i] 表示在a中取第i行的排列的哈希值(已排序)
//say[i] 表示在a中取第i列的排列的哈希值(已排序)
vector<size_t> sax, say;
vector<size_t> sbx, sby;

void solve() {
	int n, m;
	cin >> n >> m;

	pa.clear();
	a.resize(n);
	for (int i = 0; i < n;i++) {
		a[i].resize(m);
		for (int j = 0; j < m; j++) {
			cin >> a[i][j];
			pa[a[i][j]] = {i, j};
		}
	}

	pb.clear();
	b.resize(n);
	for (int i= 0; i< n;i++) {
		b[i].resize(m);
		for (int j = 0; j < m; j++) {
			cin >> b[i][j];
			pb[b[i][j]] = {i, j};
		}
	}

	sax.resize(n);
	say.resize(m);
	for (int i = 0; i < n; i++) {
		vector<int> r(m);
		for (int j = 0; j < m; j++) {
			r[j] = a[i][j];
		}
		sort(begin(r), end(r));
		stringstream ss;
		for (auto it : r) ss << it; 
		sax[i] = hash<string>{}(ss.str());
	}
	for (int i = 0; i < m; i++) {
		vector<int> r(n);
		for (int j = 0; j < n; j++) {
			r[j] = a[j][i];
		}
		sort(begin(r), end(r));
		stringstream ss;
		for (auto it : r) ss << it; 
		say[i] = hash<string>{}(ss.str());
	}

	sbx.resize(n);
	sby.resize(m);
	for (int i = 0; i < n; i++) {
		vector<int> r(m);
		for (int j = 0; j < m; j++) {
			r[j] = b[i][j];
		}
		sort(begin(r), end(r));
		stringstream ss;
		for (auto it : r) ss << it; 
		sbx[i] = hash<string>{}(ss.str());
	}
	for (int i = 0; i < m; i++) {
		vector<int> r(n);
		for (int j = 0; j < n; j++) {
			r[j] = b[j][i];
		}
		sort(begin(r), end(r));
		stringstream ss;
		for (auto it : r) ss << it; 
		sby[i] = hash<string>{}(ss.str());
	}

	for (int i = 1; i <= n*m; i++) {
		if (!(sax[pa[i].x] == sbx[pb[i].x] && say[pa[i].y] == sby[pb[i].y])) {
			cout << "NO" << endl;
			return;
		}
	}
	cout << "YES" << endl;
}

int main() {   
    ios_base::sync_with_stdio(false); 
    cin.tie(NULL); 
    cout.tie(NULL); 

	int t;
	cin >> t;
	while (t--) solve();

	return 0;
}