排列中的數值問題(改編自NOIP2018程序填空第2大題)

題目描述

對於一個 \(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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章