1 条题解
-
0
如何确定排列 ()的详细题解
问题回顾
已知一个 的排列 ,但我们不知道它的值。我们获得了一个 的网格 ,其中
即第 行第 列存放着排列中下标为 的元素。
题目保证这样的排列存在且唯一,我们需要根据网格 还原出整个排列。
核心观察
网格中出现的所有下标 的取值范围是:
- 当 时,;
- 当 时,。
因此,网格直接给出了 的全部信息,但唯独缺少 ,因为没有任何一对 能使 ()。
如何直接读出每一个 ?
我们只需选择合适的 ,使得 ,即可从 获得 。由于本题只要求输出排列,我们可以任意选择一种合法的方式读取。下面的取法是最简洁的,它用到了网格的第一行和最后一行。
对于
取第一行(),列号 。
此时 ,且因为 ,所以 ,属于网格有效列范围。
因此:直观理解:网格第一行对应 ,所有和从 到 。
因此第一行覆盖了 到 。我们只用其中的 , 留到后面处理。对于
取最后一行(),列号 。
此时 ,并且 确保 ,而 确保 ,均合法。
因此:直观理解:网格最后一行对应 ,所有和从 ()到 (),恰覆盖 。
两张图的覆盖关系
- 第一行给了
- 最后一行给了
实际使用中,我们可以仅用第一行的前 列得到 ,用最后一行的所有列得到 ,这样每个 () 都被唯一且明确地读取到。
边界验证
- 当 ,,成立。
- 当 ,,列号 有效。
- 当 ,,成立。
- 当 ,,成立。
唯一未出现的
我们已经确定了 ,而整个排列是 的一个排列,因此 就是那个在 中唯一没有出现过的整数。
实现时,可以用一个布尔数组
vis[1..2n],初始均为false,在读取网格时,对于每个 ,设vis[G_{i,j}] = true。最后扫描vis,找到仍为false的那个下标,它就是 。这也正是标准程序中使用的方法。算法流程
- 读入 和网格 。
- 初始化一个大小为 的数组
p和布尔数组used。 - 对于每个 :
- 读取 ;
- 记录 ,并标记 。
- 按顺序输出 :
- 若 ,直接输出(这部分是 的情况);
- 否则(仅 时发生),在 中找到未使用的数输出,并标记已用。
- 输出换行。
该算法的时间复杂度为 (主要来自读入网格),空间复杂度为 (除去存储网格的 ,也可以不保存网格而直接边读边处理,但通常题目允许保存)。
为什么这种读法是唯一正确的?
因为题目保证网格对应一个合法的排列,那么对于每个 ,网格中可能存在多个位置 满足 ,但它们必须全部相等(否则与排列的定义矛盾)。因此我们可以任取一个位置来获取 。上述方法只是选择了其中最方便的位置,不会产生歧义。
完整代码
#include <bits/stdc++.h> using namespace std; #define int long long void solve() { int n; cin >> n; vector<int> ans(2*n+1, 0); vector<bool> used(2*n+1, false); for (int i = 1; i <= n; i++) { for (int j = 1; j <= n; j++) { int x; cin >> x; ans[i + j] = x; used[x] = true; } } for (int i = 1; i <= 2 * n; i++) { if (ans[i] != 0) cout << ans[i] << " "; else { for (int j = 1; j <= n * 2; j++) { if (!used[j]) { used[j] = true; cout << j << " "; break; } } } } cout << "\n"; } signed main() { ios_base::sync_with_stdio(0); cin.tie(0); int t; cin >> t; while (t--) solve(); return 0; }
- 1
信息
- ID
- 6740
- 时间
- 2000ms
- 内存
- 256MiB
- 难度
- 2
- 标签
- 递交数
- 3
- 已通过
- 1
- 上传者