Description
老师要将N张试卷重新排序,每张试卷都有编号为1∼N,采取如下的方法: 先将编号1的试卷放进队列,剩下从第2张到第N张依次放入队列, 放入的时候老师会把编号i的试卷插入到编号为x试卷之前或之后(x<i), 在老师完成这N-1次操作之后,请输出试卷序列的最终编号。
输入格式
第1行为一个正整数N,表示了有N张试卷。 第2-N行,第i行包含两个整数x,p,其中x为小于i的正整数,p为0或者1。 若p为0,则表示将编号为i的试卷放入编号为x试卷的左边,为1则放入x试卷的右边。
输出格式
输出N个整数,表示完成插入后试卷系列中试卷的编号(1<=N<=100000)。
输入样例
4 1 0 2 1 1 0
输出样例
2 3 4 1
提示
4张试卷。第二行1,0,将编号2的试卷放入编号1的左边,则序列为2 1; 第三行2,1,将编号3试卷放入编号2试卷的右边,则序列为2 3 1; 第四行1 0,将编号4的试卷放入编号1的左边,则序列为2 3 4 1。
#include <iostream>
#include <vector>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int N;
cin >> N;
vector<int> prev(N + 1), next(N + 1); // prev[i] 和 next[i] 分别表示节点i的前驱和后继
// 初始化第一个节点(编号为1)
prev[1] = 0;
next[1] = 0;
int head = 1, tail = 1; // 链表头和尾的编号
for (int i = 2; i <= N; ++i) {
int x, p;
cin >> x >> p;
if (p == 0) { // 插入到x的左边
prev[i] = prev[x]; // i的前驱是x的原前驱
next[i] = x; // i的后继是x
if (prev[x] != 0) { // 如果x不是头节点
next[prev[x]] = i; // 原前驱的后继更新为i
} else {
head = i; // 更新头节点为i
}
prev[x] = i; // x的前驱更新为i
} else { // 插入到x的右边
next[i] = next[x]; // i的后继是x的原后继
prev[i] = x; // i的前驱是x
if (next[x] != 0) { // 如果x不是尾节点
prev[next[x]] = i; // 原后继的前驱更新为i
} else {
tail = i; // 更新尾节点为i
}
next[x] = i; // x的后继更新为i
}
}
// 从头节点开始遍历输出
int curr = head;
while (curr != 0) {
cout << curr << " ";
curr = next[curr];
}
return 0;
}
prev 数组的存在意义
是为了支持 双向链表的操作,它在某些场景下非常有用。以下是 prev 数组的意义和具体作用:
1. 支持双向遍历
-
单向链表:
-
只能从头节点开始,沿着
next指针单向遍历。 -
无法从尾节点反向遍历。
-
-
双向链表:
-
既可以从头节点开始,沿着
next指针正向遍历。 -
也可以从尾节点开始,沿着
prev指针反向遍历。
-
示例
假设链表结构如下:
复制
prev[1] = 0, next[1] = 2 prev[2] = 1, next[2] = 3 prev[3] = 2, next[3] = 0
-
正向遍历(使用
next):复制
1 -> 2 -> 3
-
反向遍历(使用
prev):复制
3 -> 2 -> 1
2. 支持高效的插入和删除操作
在双向链表中,prev 数组使得插入和删除操作更加高效和灵活。
插入操作
-
插入到目标节点的左侧:
-
新节点的
prev指向目标节点的原前驱。 -
新节点的
next指向目标节点。 -
目标节点的原前驱的
next指向新节点。 -
目标节点的
prev指向新节点。
-
-
插入到目标节点的右侧:
-
新节点的
prev指向目标节点。 -
新节点的
next指向目标节点的原后继。 -
目标节点的原后继的
prev指向新节点。 -
目标节点的
next指向新节点。
-
如果没有 prev 数组,插入到左侧时需要遍历链表找到目标节点的前驱,时间复杂度为 O(N)。而有了 prev 数组,插入操作可以在 O(1) 时间内完成。
删除操作
-
删除目标节点:
-
目标节点的前驱的
next指向目标节点的后继。 -
目标节点的后继的
prev指向目标节点的前驱。
-
如果没有 prev 数组,删除操作需要遍历链表找到目标节点的前驱,时间复杂度为 O(N)。而有了 prev 数组,删除操作可以在 O(1) 时间内完成。
3. 维护链表完整性
-
在双向链表中,
prev和next数组共同维护链表的完整性。 -
每次插入或删除节点时,都需要同时更新
prev和next数组,确保链表的前后关系正确。
4. 应用场景
-
需要反向遍历的场景:
-
例如,从链表尾部开始查找或输出数据。
-
-
频繁插入和删除的场景:
-
例如,实现 LRU 缓存(最近最少使用缓存)时,需要快速移动节点到链表头部或删除尾部节点。
-
-
需要快速查找前驱节点的场景:
-
例如,某些算法需要同时访问当前节点的前驱和后继。
-
3710

被折叠的 条评论
为什么被折叠?



