Hidden Permutation隐藏排列

通信题:矩阵构造与排列还原

一、题目描述

这是一道通信题(Communication Problem),你的程序会在两个独立进程中运行两次。

First Run

给定一个 1 ~ n 的排列 p = (p1, p2, ..., pn)。

输出一个 n × n 矩阵 A,要求:

  • 每一行都是 1 ~ n 的一个排列
  • 任意两行互不相同

Second Run

评测机随机打乱 A 的行顺序(每行内部元素顺序不变),得到矩阵 B。

给定 B,要求还原出第一次运行时的原始排列 p。

两次运行之间不保留任何进程状态,B 是唯一传递的信息。

对应题目:Hidden Permutation

二、思路分析

核心问题

second 阶段拿到的矩阵 B 是 A 的行被打乱后的结果。

关键:行顺序丢失了,但每行的内容完好。我们要利用矩阵的结构,从 B 中还原出 p。

first 阶段的构造

构造一个 n × n 矩阵 A,要求每行是排列,且两两不同。

代码采用的方式:

  • 第 1 行:直接填入 p
  • 第 2 行:把 p 反转(但保持第一个元素不变)
  • 第 3 ~ n 行:把 p 循环右移 i - 2 位

具体构造:

第 1 行:p[1], p[2], ..., p[n]
第 2 行:p[1], p[n], p[n-1], ..., p[2]
第 3 行:p[n], p[1], p[2], ..., p[n-1]     (右移 1 位)
第 4 行:p[n-1], p[n], p[1], ..., p[n-2]   (右移 2 位)
...

second 阶段的还原

观察第 1 列(a[i][1]):

  • 第 1 行第 1 列 = p[1]
  • 第 2 行第 1 列 = p[1]
  • 第 3 行第 1 列 = p[n]
  • 第 4 行第 1 列 = p[n-1]
  • ……

发现规律:第 1 行和第 2 行的第 1 列都是 p[1],其余行的第 1 列各不相同。

所以在 B 的第 1 列中:

  • 有一个值会出现两次 → 这个值就是 p[1]
  • 其余值各出现一次

利用这个信息:

  1. 找到第 1 列出现两次的值 t,它等于 p[1]
  2. 找到第 1 列只出现一次的那一行 tt(这行就是原来的第 3 ~ n 行之一)
  3. 在 tt 行中找到 t 的位置 cnt
  4. 从 cnt 开始循环输出 tt 行的元素,就还原出 p

为什么从 cnt 开始循环输出能还原 p?

因为第 3 ~ n 行都是 p 的循环移位。在 tt 行中找到 p[1] 的位置 cnt,说明这一行的 cnt 位置就是 p[1]。从 cnt 开始读这一行,相当于把循环移位“逆回去”,得到原始顺序。

三、AC 代码

#include<bits/stdc++.h>
using namespace std;

const int N = 2005;
int a[N][N], n;
int arr[N];

void output()
{
    for (int i = 1; i <= n; i++)
    {
        for (int k = 1; k <= n; k++)
        {
            if (k != 1) cout << ' ';
            cout << a[i][k];
        }
        cout << endl;
    }
}

int main()
{
    string op;
    cin >> op;

    if (op == "first")
    {
        cin >> n;
        for (int i = 1; i <= n; i++)
            cin >> a[1][i];

        // 第 2 行:保持第一个元素,其余反转
        a[2][1] = a[1][1];
        for (int i = 2; i <= n; i++)
            a[2][i] = a[1][n - i + 2];

        // 第 3 ~ n 行:第 1 行循环右移 i - 2 位
        for (int i = 3; i <= n; i++)
            for (int k = 1; k <= n; k++)
                a[i][k] = a[1][(k + i - 2 - 1) % n + 1];

        output();
    }
    else if (op == "second")
    {
        cin >> n;
        int t;
        // 统计第 1 列每个值的出现次数
        for (int i = 1; i <= n; i++)
        {
            for (int k = 1; k <= n; k++)
            {
                cin >> a[i][k];
                if (k == 1)
                {
                    arr[a[i][k]]++;
                    if (arr[a[i][k]] == 2) t = a[i][k];
                }
            }
        }

        // 找到第 1 列只出现一次的那一行
        int tt = 1;
        while (arr[a[tt][1]] != 1) tt++;

        // 在 tt 行中找到 t 的位置
        int cnt;
        for (int i = 1; i <= n; i++)
        {
            if (a[tt][i] == t)
            {
                cnt = i;
                break;
            }
        }

        // 从 cnt 开始循环输出,还原 p
        for (int i = 1; i <= n; i++)
        {
            if (i != 1) cout << ' ';
            cout << a[tt][(cnt + i - 1 - 1) % n + 1];
        }
        cout << endl;
    }
    return 0;
}

四、代码说明

1. first 阶段的构造

a[2][1] = a[1][1];
for (int i = 2; i <= n; i++)
    a[2][i] = a[1][n - i + 2];

第 2 行:第一个元素保持 p[1],其余元素反转。这样第 2 行的第 1 列和第 1 行的第 1 列都是 p[1]。

a[i][k] = a[1][(k + i - 2 - 1) % n + 1];

第 3 ~ n 行:第 1 行循环右移 i - 2 位。用取模实现循环。

2. second 阶段的还原

arr[a[i][k]]++;
if (arr[a[i][k]] == 2) t = a[i][k];

遍历第 1 列,统计每个值出现次数。出现两次的是 p[1]。

int tt = 1;
while (arr[a[tt][1]] != 1) tt++;

找到第 1 列只出现一次的那一行,这行是原来的第 3 ~ n 行之一。

for (int i = 1; i <= n; i++)
    if (a[tt][i] == t) { cnt = i; break; }

在这一行中找到 p[1] 的位置 cnt。

cout << a[tt][(cnt + i - 1 - 1) % n + 1];

从 cnt 开始循环输出这一行,就还原出了原始排列 p。

五、反思:

比赛的时候没有理解题目本身的含义,本质上还是刷题少了,之前没怎么碰到通信,
题目虽然是说代码运行两次其实是只有一个代码,这个通信也就是来构造情况让矩阵和编码相互转化,理解题目之后自行构造方案即可

六、总结

要点 内容
题型 通信题(Communication Problem),两次独立运行
first 任务 构造 n × n 矩阵,第 2 行反转,其余行循环右移
second 任务 利用第 1 列出现两次的值定位 p[1],循环输出还原
核心技巧 让 p[1] 在矩阵中出现两次,其余位置唯一,作为还原的锚点
posted @ 2026-09-28 22:55  leaderling  阅读(4)  评论(0)    收藏  举报