排列中的数值问题(改编自NOIP2018程序填空第2大题)

发布时间 2023-09-11 20:28:51作者: quanjun

题目描述

对于一个 \(1\)\(n\) 的排列 \(p_1, p_2, \ldots, p_n\)(即 \(1\)\(n\) 中每一个数在数列 \(p\) 中出现了恰好一次),令 \(q_i\) 为第 \(i\) 个位置之后第一个比 \(p_i\) 值更大的位置,如果不存在这样的位置,则 \(q_i = n + 1\)

举例来说,如果 \(n = 5\)\(p\)\(\{ 1, 5, 4, 2, 3 \}\),则 \(q\)\(\{ 2, 6, 6, 5, 6 \}\)

现在给你排列 \(p\),求出它对应的数列 \(q\)

输入格式

第一行,一个整数 \(n(1 \le n \le 10^5)\)

第二行,\(n\) 个整数 \(p_1, p_2, \ldots, p_n\)。数据保证 \(p\) 是一个 \(1\)\(n\) 的排列。

输出格式

输出共一行,包含 \(n\) 个整数 \(q_1, q_2, \ldots, q_n\),两两之间以一个空格分隔。

样例输入

5
1 5 4 2 3

样例输出

2 6 6 5 6

题解

用双向链表来解决这个问题。

首先,排列 \(p_1, p_2, \ldots, p_n\) 中的每个数字都对应有一个点,\(p_i\)(下标为 \(i\) 的元素)对应双向链表里面编号为 \(i\) 的那个节点。

特殊地,节点 \(1\) 左边额外创建一个编号为 \(0\) 的点;节点 \(n\) 右边创建一个编号为 \(n + 1\) 的点。

如下:

\(\mathtt{(0)} \leftrightarrow (1) \leftrightarrow (2) \leftrightarrow (3) \leftrightarrow \ldots \leftrightarrow (n-1) \leftrightarrow (n) \leftrightarrow \mathtt{(n+1)}\)

其中:节点 \(0\)\(n+1\) 是两个特殊的点,他们的作用主要是方便待会儿删除节点。

对于这个双向链表中编号为 \(1 \sim n\) 范围内的所有点来说,数值最小的那个点具有的性质是(假设当前双向链表中数值最小的那个节点是节点 \(x\)):

这个链表当中其它节点对应的数值都比它大。

从而可以推导出:

这个节点对应的元素右边离他最近的那个数值大于它的元素的下标就是这个节点的有指针指向的那个节点的编号。

问题来了?如何确定第 \(i\) 小的数的下标。
答:因为 \(p\) 是一个排列,所以我们可以再开一个数组 a[],用 \(a[x]\) 表示第 \(x\) 小的元素的下标。

接着,当我们输入 \(p_i\) 之后,可以得到 \(a[p_i] = i\)

然后,在创建到双向链表后,就可以循环 \(i = 1 \to n\),求 \(a[i]\) 的有指针指向的下标(就是 \(q_{a[i]}\)

\(q_i\) 可以如何表示?

因为在删除节点 \(a[i]\) 的时候,\(r_{a[i]}\) 就是 \(q_{a[i]}\),而删除节点 \(a[i]\) 的时候,没有改变 \(r_{a[i]}\)(以后也不会改变),所以 \(r_{a[i]}\) 对应的就是 \(q_{a[i]}\)

\(\Rightarrow\) \(r_i\) 就是 \(q_i\)

示例程序:

#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e5 + 5;
int n, x, l[maxn], r[maxn], a[maxn];

int main() {
    cin >> n;
    for (int i = 1; i <= n; i++) {
        cin >> x;
        a[x] = i;
    }
    for (int i = 0; i <= n; i++) {
        r[i] = i + 1;
        l[i + 1] = i;
    }
    for (int i = 1; i <= n; i++) {
        x = a[i];
        r[l[x]] = r[x];
        l[r[x]] = l[x];
    }
    for (int i = 1; i <= n; i++)
        cout << r[i] << " ";
    return 0;
}