CF 1980E - Permutation of Rows and Columns
We are given two rectangular grids of numbers, both of size $n times m$, and together they contain exactly the numbers from $1$ to $n cdot m$, each appearing once. So each matrix is just a rearrangement of the same set of tiles.
CF 1980E - Permutation of Rows and Columns
Rating: 1600
Tags: constructive algorithms, data structures, greedy, hashing, implementation, math, matrices, sortings
Solve time: 1m 20s
Verified: yes
Solution
Problem Understanding
We are given two rectangular grids of numbers, both of size $n \times m$, and together they contain exactly the numbers from $1$ to $n \cdot m$, each appearing once. So each matrix is just a rearrangement of the same set of tiles.
The only allowed moves are swapping entire rows or swapping entire columns. We can do this any number of times. The task is to determine whether we can transform the first grid into the second using only these row and column swaps.
The key constraint is that row swaps do not change the order inside a row, they only reorder rows, and column swaps do not change the order inside a column, they only reorder columns. So the structure of the grid is preserved up to independent permutations of row indices and column indices.
The input size is large: across all test cases, the total number of cells is at most $2 \cdot 10^5$. This strongly suggests an $O(nm)$ per test case or linear overall solution. Anything involving repeated sorting per test or simulation of operations is fine only if it is linear in total input size.
A naive misunderstanding would be to think we need to simulate swaps or try to “align” rows and columns greedily. That would fail in cases like:
Example:
$$a = \begin{pmatrix} 1 & 2 \ 3 & 4 \end{pmatrix}, \quad b = \begin{pmatrix} 4 & 3 \ 2 & 1 \end{pmatrix}$$
A naive row-matching approach might conclude mismatch because no row matches directly, but swapping both rows and columns makes the transformation possible.
Another subtle failure case is assuming row multisets must match row-by-row. That is wrong because rows themselves are permuted arbitrarily.
The real difficulty is that both row and column permutations interact globally rather than locally.
Approaches
The brute-force viewpoint is to think of applying row and column swaps as generating all permutations of rows and columns. That means any transformation corresponds to choosing a permutation of rows and a permutation of columns. In principle, we could try all row permutations and column permutations and check if the resulting matrix matches. That would involve $n!\cdot m!$ possibilities, which is completely infeasible even for $n=5$.
The key insight is that row and column swaps act independently on indices. If we assign each value its position in both matrices, we can think in terms of coordinates. Every number $x$ in matrix $a$ has coordinates $(r_a[x], c_a[x])$, and in matrix $b$, coordinates $(r_b[x], c_b[x])$.
If a valid transformation exists, then there must exist a permutation of rows and a permutation of columns such that for every value $x$, its row index maps consistently and its column index maps consistently. In other words, the transformation induces two bijections:
one on row indices and one on column indices, and they must simultaneously explain all placements.
This reduces the problem to checking consistency of these induced mappings. A clean way to enforce this is to anchor row relationships using positions of values: values appearing in the same row in $a$ must map to values appearing in the same row in $b$, after reindexing rows consistently. The same applies to columns.
A simpler and standard reformulation is to treat each value as a pair $(row, col)$, then observe that the relative ordering of rows between any two values must be preserved between $a$ and $b$, and similarly for columns. This induces a constraint graph where inconsistencies immediately reveal impossibility.
A more implementation-friendly characterization is to normalize both matrices by replacing values with their positions and checking whether the induced row-ordering and column-ordering of all values is identical up to permutation. Sorting values by their positions in one matrix and verifying consistency in the other leads to a linear or near-linear check using hashing or signature comparisons per row and column.
The most practical solution uses positional encoding: for each value, record its row and column in both matrices, then verify that the relative ordering of rows and columns among all values is identical in both representations. This can be reduced to checking that the mapping from rows in $a$ to rows in $b$ is consistent, and similarly for columns.
| Approach | Time Complexity | Space Complexity | Verdict |
|---|---|---|---|
| Brute force permutations of rows/cols | $O(n! \cdot m!)$ | $O(nm)$ | Too slow |
| Positional consistency mapping | $O(nm)$ | $O(nm)$ | Accepted |
Algorithm Walkthrough
We build a direct mapping between positions of values in both matrices and verify that row and column permutations implied by these mappings are consistent.
- Read both matrices and store, for every value $x$, its coordinates in $a$: $(r_a[x], c_a[x])$ and in $b$: $(r_b[x], c_b[x])$.
This step converts the grid problem into a point-matching problem over permutations. 2. For each value $x$, we interpret the transformation as mapping row $r_a[x]$ to $r_b[x]$, and column $c_a[x]$ to $c_b[x]$.
If a valid global row permutation exists, then all values sharing a row in $a$ must map consistently to the same row structure in $b$. 3. We enforce consistency of row mapping by selecting one representative value per row in $a$, and ensuring all values in that row map to the same row permutation in $b$.
If any row in $a$ tries to map to multiple rows in $b$, transformation is impossible. 4. Repeat the same logic for columns, ensuring each column in $a$ consistently maps to a single column in $b$. 5. After establishing mappings, we verify injectivity: no two distinct rows in $a$ map to the same row in $b$, and similarly for columns.
This ensures the mapping is a true permutation rather than a collapse. 6. If both row and column mappings are valid permutations, output YES; otherwise output NO.
Why it works
Row swaps only permute row indices globally, meaning the identity of which values share a row is invariant under any sequence of operations. The same holds for columns. So the partition of values induced by rows (and by columns) must be preserved between $a$ and $b$, up to renaming of row indices and column indices.
If two values are in the same row in $a$, they must end up in the same row in $b$ after applying the row permutation. This forces a well-defined mapping from rows of $a$ to rows of $b$. The same argument applies in reverse, ensuring bijectivity.
Thus the algorithm is checking whether the row-partition and column-partition induced by the permutation of values can be consistently relabeled.
Python Solution
import sys
input = sys.stdin.readline
def solve():
t = int(input())
out = []
for _ in range(t):
n, m = map(int, input().split())
pos_a = {}
pos_b = {}
for i in range(n):
row = list(map(int, input().split()))
for j, x in enumerate(row):
pos_a[x] = (i, j)
for i in range(n):
row = list(map(int, input().split()))
for j, x in enumerate(row):
pos_b[x] = (i, j)
row_map = {}
col_map = {}
ok = True
for x in pos_a:
ra, ca = pos_a[x]
rb, cb = pos_b[x]
if ra in row_map and row_map[ra] != rb:
ok = False
break
if ca in col_map and col_map[ca] != cb:
ok = False
break
row_map[ra] = rb
col_map[ca] = cb
if ok:
if len(set(row_map.values())) != len(row_map):
ok = False
if len(set(col_map.values())) != len(col_map):
ok = False
out.append("YES" if ok else "NO")
print("\n".join(out))
if __name__ == "__main__":
solve()
The solution first compresses both matrices into position lookup tables so each value directly reveals its coordinates. Then it builds two partial mappings: one for rows and one for columns. The consistency checks ensure that a row in the first matrix never tries to map to two different rows in the second matrix, which would contradict the existence of a global row permutation.
The final injectivity check ensures we truly have permutations, not many-to-one mappings, which would violate reversibility of row and column swaps.
A common pitfall is forgetting that consistency must be enforced globally across all values, not row-by-row independently.
Worked Examples
Example 1
Input:
1
2 2
1 2
3 4
4 3
2 1
| value | pos in a | pos in b | row map | col map |
|---|---|---|---|---|
| 1 | (0,0) | (1,1) | 0→1 | 0→1 |
| 2 | (0,1) | (1,0) | 0→1 | 1→0 |
| 3 | (1,0) | (0,1) | 1→0 | 0→1 |
| 4 | (1,1) | (0,0) | 1→0 | 1→0 |
All mappings are consistent and bijective.
This shows that even when both rows and columns are fully reversed, the mapping remains valid as long as consistency is preserved.
Example 2
Input:
1
2 2
1 2
3 4
4 1
2 3
| value | pos in a | pos in b | row map | col map |
|---|---|---|---|---|
| 1 | (0,0) | (1,1) | 0→1 | 0→1 |
| 2 | (0,1) | (0,1) | 0→0 | 1→1 |
| 3 | (1,0) | (1,0) | 1→1 | 0→0 |
| 4 | (1,1) | (0,0) | 1→0 | 1→0 |
Row mapping becomes inconsistent: row 0 maps to both 1 and 0. This violates the requirement of a single global row permutation.
This demonstrates how a single conflict in induced mappings invalidates the transformation.
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | $O(nm)$ | Each value is processed once to build positions and verify mappings |
| Space | $O(nm)$ | Storing position maps for all values |
The solution scales linearly with the total number of elements across all test cases, which fits comfortably under the constraint of $2 \cdot 10^5$ total cells.
Test Cases
import sys, io
def run(inp: str) -> str:
sys.stdin = io.StringIO(inp)
from collections import defaultdict
def solve():
t = int(input())
out = []
for _ in range(t):
n, m = map(int, input().split())
pa, pb = {}, {}
for i in range(n):
for j, x in enumerate(map(int, input().split())):
pa[x] = (i, j)
for i in range(n):
for j, x in enumerate(map(int, input().split())):
pb[x] = (i, j)
rm, cm = {}, {}
ok = True
for x in pa:
ra, ca = pa[x]
rb, cb = pb[x]
if ra in rm and rm[ra] != rb:
ok = False
break
if ca in cm and cm[ca] != cb:
ok = False
break
rm[ra] = rb
cm[ca] = cb
if ok:
if len(set(rm.values())) != len(rm):
ok = False
if len(set(cm.values())) != len(cm):
ok = False
out.append("YES" if ok else "NO")
return "\n".join(out)
return solve()
# provided samples
assert run("""7
1 1
1
1
2 2
1 2
3 4
4 3
2 1
2 2
1 2
3 4
4 3
1 2
3 4
1 5 9 6
12 10 4 8
7 11 3 2
1 5 9 6
12 10 4 8
7 11 3 2
3 3
1 5 9
6 4 2
3 8 7
9 5 1
2 4 6
7 8 3
2 3
1 2 6
5 4 3
6 1 2
3 4 5
1 5
5 1 2 3 4
4 2 5 1 3
""") == """YES
YES
NO
YES
YES
NO
YES"""
# custom cases
# minimum size
assert run("""1
1 1
1
1
""") == "YES"
# impossible mapping
assert run("""1
2 2
1 2
3 4
1 3
2 4
""") == "NO"
# row-only swap
assert run("""1
2 3
1 2 3
4 5 6
4 5 6
1 2 3
""") == "YES"
# column-only swap
assert run("""1
3 2
1 2
3 4
5 6
2 1
4 3
6 5
""") == "YES"
| Test input | Expected output | What it validates |
|---|---|---|
| 1×1 identity | YES | trivial base case |
| inconsistent mapping | NO | detects row/column conflict |
| row permutation only | YES | row swaps alone sufficient |
| column permutation only | YES | column swaps alone sufficient |
Edge Cases
A tricky edge case is when row mappings are locally consistent but globally collide. For example, if two different rows in $a$ both try to map into the same row in $b$, the algorithm catches this via injectivity check.
Consider:
a:
1 2
3 4
b:
1 2
3 4
Here identity mapping is valid.
Now modify:
b:
3 4
1 2
Row mapping becomes 0→1 and 1→0, which is fine. But if a third row existed trying to map inconsistently, the conflict would appear only when checking global uniqueness of mapped values, not per assignment.
The algorithm correctly handles this by enforcing both consistency and bijection constraints over the entire mapping space, rather than relying on local row comparisons.