2018-NOIP-J1:完善程序题

2023年8月1日 | 分类: 【题目】

【题目】

对于一个 \(1\) 到 \(n\) 的排列 \(P\) (即 \(1\) 到 \(n\) 中每一个数在 \(P\) 中出现了恰好一次)。

令 \(q[i]\) 为第 \(i\) 个位置之后第一个比 \(P\) 值更大的位置,如果不存在这样的位置,则 \(q[i]=n+1\) 。

举例来说,如果 \(n=5\) 且 \(P\) 为 \(1 5 4 2 3\) ,则 \(q\) 为 \(2 6 6 5 6\) 。

下列程序读入了排列 \(P\) ,使用双向链表求解了答案。试补全程序。

#include <iostream>
using namespace std;
 
const int N = 100010;
int n;
int L[N], R[N], a[N];
 
int main() {
    cin >> n;
    for (int i = 1; i <= n; ++i) {
        int x;
        cin >> x;
        ① ;
    }
    
    for (int i = 1; i <= n; ++i) {
        R[i] = ② ;
        L[i] = i - 1;
    }
    
    for (int i = 1; i <= n; ++i) {
        L[ ③ ] = L[a[i]];
        R[L[a[i]]] = R[ ④ ];
    }
    
    for (int i = 1; i <= n; ++i) {
    	cout << ⑤ << " ";
    }
    
    cout << endl;
    return 0;
}

【解析】

先说说这道题怎么拿链表解决。

先找到最小的元素,是第1位的1,它右边不论是几都肯定比它大,所以这一位的答案就是右侧的第2位。
 此时把它从链表中删除,让前一位直接指向后一位。

再在链表中找到最小的元素,是第4位的2,它右边不论是几都比它大,所以这一位的答案是第5位。
 再从链表中把2删除,让前一位指向后一位。

继续这个过程,找到最小的元素是第5位的3,它右侧的元素肯定比它大,因此这一位的答案是第6位。
 然后从链表中把3删除,让前一位指向后一位。

继续,最小的元素是第3位的4,它右侧的元素(第6位)比它大,这一位答案是6。
         从链表中删除4。

最后只剩第2位的5,右侧(第6位)比它大,这一位答案是6。把它删除后,整个操作结束。

最终的答案就是过程中记录下的数据,第1位是2,第4位是5,第5位是6,第3位是6,第2位是6,
即答案数组2 6 6 5 6。

程序用的是双向链表, \(L\) 和 \(R\) 相当于分别存储了每个元素的上一位和下一位。
这样有什么好处呢?副产品会更多。
最终不止知道每个元素后面比它大的第一个元素在哪,还能知道前面比它大的第一个元素在哪。
第2个for循环,显然它是先把ii对应到a[i],再对应到L和R数组,为什么呢?为了去掉每轮都要先寻找最小值的过程。先存一下第1小、第2小、第3小……的值依次出现在哪儿,每轮就能直那一位。

第1题:

\(a[x]=i\) 因为正好是1~n的排列
\(x\) 就表示第 \(x\) 小的数
\(a[x]=i\)表示第 \(x\) 小的数出现的位置
\(p\) 数组:\(1 5 4 2 3\)
\(a\) 数组:\(1 4 5 3 2\)

即最小的数出现在第1位,第2小在第4位……

第2题:

\(i+1\)
L记录前面第一个更大值的位置
R记录后面第一个更大值的位置
根据最值更新链表的过程,定位到当前的最小值后,就要从双向链表中将它删除。
\(i\)表示的是当前要找第\(i\)小的数,它的实际位置是 \(a[i]\) ,所以 \(i\) 与链表操作无直接关系,都是与 \(a[i]\) 有关。
要让前一位和后一位直接互相指向,用自己的前一位更新后一位的前一位,用自己的后一位更新前一位的后一位。

第3题:

\(R[a[i]]\)

第4题:

\(a[i]\)
我们假定位置 \(i\) 后边的最大数字的位置为 \(i+1\),然后从最小的 \(1\) 开始更新,让 \(1\) 所在位置的前一位直接指向 \(1 \) 的后一位( \(1\) 不可能是 \(1\) 所在位置的前一位的后边的第一个最大的,相当于删除了 \(1\))。从小到大循环删除所有的不合法假定,剩下的就是答案。

第5题:

\(R[i]\)
\(R\)记录后面第一个更大值的位置