1 条题解

  • 0
    @ 2026-5-3 12:55:56

    如何确定排列 pip_i2i2n2 \le i \le 2n)的详细题解

    问题回顾

    已知一个 2n2n 的排列 p1,p2,,p2np_1, p_2, \dots, p_{2n},但我们不知道它的值。我们获得了一个 n×nn \times n 的网格 GG,其中

    Gi,j=pi+j(1i,jn)G_{i,j} = p_{i+j} \qquad (1 \le i, j \le n)

    即第 ii 行第 jj 列存放着排列中下标为 i+ji+j 的元素。

    题目保证这样的排列存在且唯一,我们需要根据网格 GG 还原出整个排列。

    核心观察

    网格中出现的所有下标 i+ji+j 的取值范围是:

    • i=j=1i = j = 1 时,i+j=2i+j = 2
    • i=n,j=ni = n, j = n 时,i+j=2ni+j = 2n

    因此,网格直接给出了 p2,p3,,p2np_2, p_3, \dots, p_{2n} 的全部信息,但唯独缺少 p1p_1,因为没有任何一对 (i,j)(i,j) 能使 i+j=1i+j = 1i,j1i,j \ge 1)。

    如何直接读出每一个 pkp_k

    我们只需选择合适的 (i,j)(i,j),使得 i+j=ki+j = k,即可从 Gi,jG_{i,j} 获得 pkp_k。由于本题只要求输出排列,我们可以任意选择一种合法的方式读取。下面的取法是最简洁的,它用到了网格的第一行和最后一行。

    对于 2kn2 \le k \le n

    取第一行(i=1i = 1),列号 j=k1j = k - 1
    此时 i+j=1+(k1)=ki+j = 1 + (k-1) = k,且因为 knk \le n,所以 j=k1n1<nj = k-1 \le n-1 < n,属于网格有效列范围。
    因此:

    pk=G1,k1(2kn)p_k = G_{1, k-1} \quad (2 \le k \le n)

    直观理解:网格第一行对应 i=1i=1,所有和从 1+1=21+1=21+n=n+11+n=n+1
    因此第一行覆盖了 p2p_2pn+1p_{n+1}。我们只用其中的 p2pnp_2 \dots p_npn+1p_{n+1} 留到后面处理。

    对于 n+1k2nn+1 \le k \le 2n

    取最后一行(i=ni = n),列号 j=knj = k - n
    此时 i+j=n+(kn)=ki+j = n + (k - n) = k,并且 kn+1k \ge n+1 确保 j1j \ge 1,而 k2nk \le 2n 确保 jnj \le n,均合法。
    因此:

    pk=Gn,kn(n+1k2n)p_k = G_{n, k-n} \quad (n+1 \le k \le 2n)

    直观理解:网格最后一行对应 i=ni=n,所有和从 n+1n+1j=1j=1)到 2n2nj=nj=n),恰覆盖 pn+1p2np_{n+1} \dots p_{2n}

    两张图的覆盖关系

    • 第一行给了 p2,p3,,pn+1p_2, p_3, \dots, p_{n+1}
    • 最后一行给了 pn+1,pn+2,,p2np_{n+1}, p_{n+2}, \dots, p_{2n}

    实际使用中,我们可以仅用第一行的前 n1n-1 列得到 p2pnp_2 \dots p_n,用最后一行的所有列得到 pn+1p2np_{n+1} \dots p_{2n},这样每个 pkp_k (k2k \ge 2) 都被唯一且明确地读取到。

    边界验证

    • k=2k=2G1,1=p2G_{1,1} = p_2,成立。
    • k=nk=nG1,n1=pnG_{1,n-1} = p_n,列号 n1n-1 有效。
    • k=n+1k=n+1Gn,1=pn+1G_{n,1} = p_{n+1},成立。
    • k=2nk=2nGn,n=p2nG_{n,n} = p_{2n},成立。

    唯一未出现的 p1p_1

    我们已经确定了 p2,p3,,p2np_2, p_3, \dots, p_{2n},而整个排列是 12n1 \sim 2n 的一个排列,因此 p1p_1 就是那个[1,2n][1, 2n] 中唯一没有出现过的整数

    实现时,可以用一个布尔数组 vis[1..2n],初始均为 false,在读取网格时,对于每个 Gi,jG_{i,j},设 vis[G_{i,j}] = true。最后扫描 vis,找到仍为 false 的那个下标,它就是 p1p_1。这也正是标准程序中使用的方法。

    算法流程

    1. 读入 nn 和网格 GG
    2. 初始化一个大小为 2n+12n+1 的数组 p 和布尔数组 used
    3. 对于每个 i,ji, j
      • 读取 x=Gi,jx = G_{i,j}
      • 记录 p[i+j]=xp[i+j] = x,并标记 used[x]=trueused[x] = \text{true}
    4. 按顺序输出 p1p2np_1 \dots p_{2n}
      • p[i]0p[i] \neq 0,直接输出(这部分是 i2i \ge 2 的情况);
      • 否则(仅 i=1i=1 时发生),在 12n1 \sim 2n 中找到未使用的数输出,并标记已用。
    5. 输出换行。

    该算法的时间复杂度为 O(n2)O(n^2)(主要来自读入网格),空间复杂度为 O(n)O(n)(除去存储网格的 O(n2)O(n^2),也可以不保存网格而直接边读边处理,但通常题目允许保存)。

    为什么这种读法是唯一正确的?

    因为题目保证网格对应一个合法的排列,那么对于每个 k=22nk = 2 \dots 2n,网格中可能存在多个位置 (i,j)(i,j) 满足 i+j=ki+j = k,但它们必须全部相等(否则与排列的定义矛盾)。因此我们可以任取一个位置来获取 pkp_k。上述方法只是选择了其中最方便的位置,不会产生歧义。

    完整代码

    #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
    上传者