我们得到了一个 $N \times N$ 的网格。
核心线索:这个网格是由一个长度为 $2N-1$ 的序列生成的。每一行不仅代表序列的一部分,而且行与行之间存在大量的重叠。
思考:如果我们把一个长序列切成 $N$ 个长度为 $N$ 的片段,每个数字会出现几次?
假设序列为:$A, B, C, D, E$ ($N=3$)
统计出现次数:
出现1次的数字位于两端,出现N次的数字位于正中间。
我们得到了两个可能的原始序列(正序或逆序)。
题目要求输出转换后的网格,我们需要选择字典序最小的那个。
1. 找到开头元素(Freq=1)和结尾元素(Freq=1)。
2. 将所有数字映射为它们在序列中的位置($1 \dots 2N-1$)。
3. 检查网格第一个元素的值。如果它对应的位置很大(比如 $> N+1$),说明我们可能“看反了”,需要将整个映射关系翻转。
#include <bits/stdc++.h>
using namespace std;
int n;
int a[1001][1001];
int cnt[2001]; // 统计频率
int perm[2001]; // 映射表:数字 -> 最终位置值
int main() {
scanf("%d",&n);
// 读入并统计频率
for (int i=0; i<n; i++)
for (int j=0; j<n; j++) {
scanf("%d",&a[i][j]);
cnt[a[i][j]]++;
}
// 特殊情况处理
if (n == 1) { printf("2\n"); return 0; }
// 找到只出现一次的两个数(首和尾)
int first = 0, last = 0;
for (int i=2; i<=2*n; i++) {
if (cnt[i] == 1) {
if (first == 0) first = i;
else last = i;
}
}
// 找到这两个数所在的行,用于确定方向参照
int firstline, lastline;
for (int i=0; i<n; i++) {
for (int j=0; j<n; j++) {
if (a[i][j] == first) firstline = i;
if (a[i][j] == last) lastline = i;
}
}
这里利用 firstline 和 lastline 巧妙地填充了整个映射表。
// 核心逻辑:填充映射表
// firstline 包含前半部分,lastline 包含后半部分
for (int j=0; j n+1 ||
(perm[a[0][0]] == n+1 && perm[a[0][1]] > n+1)) {
for (int i = 2; i <= 2*n; i++)
perm[i] = 2*n + 2 - perm[i]; // 反转映射值
}
// 输出转换后的网格
for (int i=0; i<n; i++) {
for (int j=0; j<n; j++) {
printf("%d%s", perm[a[i][j]], j==n-1?"":" ");
}
printf("\n");
}
return 0;
}