通过将去掉空格的网格展开为一维序列,证明上下左右移动均不改变逆序对数的奇偶性,转化为归并排序统计逆序对。
OJ: roj
题目 ID: 3016
难度:普及+/提高-
标签:逆序对归并排序不变量
创建: 2026-04-18 10:00
更新: 2026-10-07 13:50
形式化题目
给定一个奇数
每次操作可以将空格与上下左右相邻的一个数字交换位置。判断是否存在一系列操作,使得第一个网格变换为第二个网格。
正解
思路
直接在
1. 展开为一维序列
将网格按行优先顺序(从左到右、从上到下)展开为一维数组,并忽略代表空格的数字
我们考查空格移动对该序列逆序对数量的影响:
-
空格左右移动:
- 空格与同行的左右相邻元素交换。
- 在忽略
的一维序列中,所有非 数字的相对前后顺序完全没有发生改变。 - 因此,左右移动不改变一维序列的逆序对数量。
-
空格上下移动:
- 空格向上或向下移动一格,相当于空格在一维网格序列中向前或向后跨越了
个位置。 - 除去空格自身,该操作等价于:某个非
数字跨过了恰好 个其他非 数字。 - 设这
个数字中,原本与该数字构成逆序对的有 个,构成顺序对的有 个。跨越后,原本的逆序变成顺序,原本的顺序变成逆序。 - 逆序对总数的变化量为:
- 模
意义下: - 题目给定
为奇数,因此 一定是偶数! - 即
。因此,上下移动同样不改变一维序列逆序对数量的奇偶性。
- 空格向上或向下移动一格,相当于空格在一维网格序列中向前或向后跨越了
2. 结论与充要性
综上,无论空格如何移动,忽略
代数置换群理论保证了其充分性:在
- 两个局面互相可达
两个局面忽略 后的序列具有相同的逆序对奇偶性。
只需对两个局面分别求出忽略
图示解析
下面以
网格状态 A: 网格状态 B(0 下移,与 7 交换):
1 2 3 1 2 3
0 4 6 ====== 0 下移 =====> 7 4 6
7 5 8 0 5 8
忽略 0 后的一维序列 A: [1, 2, 3, 4, 6, 7, 5, 8]
忽略 0 后的一维序列 B: [1, 2, 3, 7, 4, 6, 5, 8]在忽略
代码
Python 版:
#!/usr/bin/env python3
# Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
# rbook: -> https://rbook.roj.ac.cn https://rbook2.roj.ac.cn
# rainboy的学习导航网站: https://idx.roj.ac.cn
# create_at: 2026-04-18 10:00
# update_at: 2026-04-18 10:00
import sys
def count_inversions(arr: list[int]) -> int:
"""求一维数组的逆序对总数。利用归并排序在 O(M log M) 时间内统计。"""
temp = [0] * len(arr)
def merge_sort(left: int, right: int) -> int:
if left >= right:
return 0
mid = (left + right) // 2
inv = merge_sort(left, mid) + merge_sort(mid + 1, right)
i, j, k = left, mid + 1, left
while i <= mid and j <= right:
if arr[i] <= arr[j]:
temp[k] = arr[i]
i += 1
else:
temp[k] = arr[j]
inv += mid - i + 1
j += 1
k += 1
while i <= mid:
temp[k] = arr[i]
i += 1
k += 1
while j <= right:
temp[k] = arr[j]
j += 1
k += 1
arr[left : right + 1] = temp[left : right + 1]
return inv
return merge_sort(0, len(arr) - 1)
def solve() -> None:
data = iter(map(int, sys.stdin.buffer.read().split()))
out: list[str] = []
for n in data:
total_cells = n * n
# 读取两个局面并忽略空格 0
seq1 = [v for _ in range(total_cells) if (v := next(data)) != 0]
seq2 = [v for _ in range(total_cells) if (v := next(data)) != 0]
inv1 = count_inversions(seq1)
inv2 = count_inversions(seq2)
is_reachable = (inv1 & 1) == (inv2 & 1)
out.append("TAK" if is_reachable else "NIE")
print("\n".join(out))
if __name__ == "__main__":
solve()C++ 版(同一算法):
/**
* Author by Rainboy blog: https://rainboylv.com github: https://github.com/rainboylvx
* rbook: -> https://rbook.roj.ac.cn https://rbook2.roj.ac.cn
* rainboy的学习导航网站: https://idx.roj.ac.cn
* create_at: 2026-06-18 10:40
* update_at: 2026-10-06 14:05
*/
// 奇数码问题:n 为奇数时,两个局面可达 <=> 忽略空格 0 后的
// 两个一维序列逆序对个数奇偶性相同。用归并排序统计逆序对。
#include <cstdio>
typedef long long ll;
const int MAXN = 505; // n < 500
const int MAXM = MAXN * MAXN;
ll a[MAXM]; // 第一个局面忽略 0 后的一维序列
ll b[MAXM]; // 第二个局面忽略 0 后的一维序列
ll tmp1[MAXM]; // 归并辅助数组(局面一)
ll tmp2[MAXM]; // 归并辅助数组(局面二)
// 归并排序统计 a[l..r] 的逆序对个数,过程中会排好序
ll merge_sort_count(ll *arr, ll *tmp, ll l, ll r) {
if (l >= r)
return 0;
ll mid = (l + r) >> 1;
// 逆序对 = 左半内部的 + 右半内部的 + 合并时跨越两半的
ll inv = merge_sort_count(arr, tmp, l, mid) + merge_sort_count(arr, tmp, mid + 1, r);
ll i = l, j = mid + 1, k = l;
while (i <= mid && j <= r) {
if (arr[i] <= arr[j]) {
tmp[k] = arr[i];
i++;
} else {
// arr[j] 比左边剩下的 mid - i + 1 个数都小,各构成一个逆序对
tmp[k] = arr[j];
inv += mid - i + 1;
j++;
}
k++;
}
while (i <= mid) {
tmp[k] = arr[i];
i++;
k++;
}
while (j <= r) {
tmp[k] = arr[j];
j++;
k++;
}
for (ll p = l; p <= r; p++)
arr[p] = tmp[p];
return inv;
}
// 读入一个 n*n 局面(n 已在 main 中读出),把非 0 数字依次存入 arr,返回长度
ll read_board(ll *arr, int n) {
ll len = 0;
for (int i = 0; i < n * n; i++) {
ll v;
scanf("%lld", &v);
if (v != 0) { // 忽略空格 0
len++;
arr[len] = v;
}
}
return len;
}
int main() {
int n;
// 多组数据,读到文件末尾为止
while (scanf("%d", &n) == 1) {
ll len1 = read_board(a, n); // 第一个局面,忽略 0 后的长度
ll len2 = read_board(b, n); // 第二个局面
ll inv1 = merge_sort_count(a, tmp1, 1, len1);
ll inv2 = merge_sort_count(b, tmp2, 1, len2);
if ((inv1 & 1) == (inv2 & 1))
printf("TAK\n");
else
printf("NIE\n");
}
return 0;
}复杂度
- 时间复杂度:序列长度为
。归并排序求解逆序对的时间复杂度为 。若有 组数据,总时间复杂度为 ,足以在时限内通过。 - 空间复杂度:存储序列与归并辅助数组占用
空间,递归深度为 ,总空间复杂度为 。
总结
奇数码问题的本质是通过“忽略空格看相对顺序”将二维网格变换转化为一维置换。因为棋盘边长