洛谷 P1160 队列安排 题解
洛谷 P1160 队列安排 题解
题目大意
原题链接:洛谷 P1160 队列安排
将 \(N\) 个同学用以下方法排成一列:
-
先将 \(1\) 号同学安排进队列,这时队列中只有他一个人;
-
\(2\sim N\) 号同学依次入列,编号为 \(i\) 的同学入列方式为:老师指定编号为 \(i\) 的同学站在编号为 \(1\sim(i-1)\) 中某位同学(即之前已经入列的同学)的左边或右边;
-
从队列中去掉 \(M\) 个同学,其他同学位置顺序不变。若该同学已经不在队列中则忽略这一条指令。
排列完毕后,从左到右输出所有同学的编号。
题意分析
考察双向链表的使用。用数组模拟指针。定义一个结构体数组和一个首指针模拟链表:
struct list {
int llink, rlink; /* llink 指向它的前驱(排在他左边的同学),rlink 指向它的后继(排在他右边的同学) */
};
list stu[100005]; // stu[i] 表示 i 号同学的前驱和后继
int head = 1; // head 指向链首
题目要求实现如下操作:
- 插入:如图,1、2 号同学正在排队,将 3 号同学插入到 1、2 号同学之间,需要将 1 号同学的后继改为 3 号同学,2 号同学的前驱改为 3 号同学,3 号同学的前驱改为 1 号同学、后继改为 2 号同学。

void ins(int i, int k, int p) {
if (p == 0) {
if (head == k) head = i; // 注意处理 k 为队首的情况
stu[i].llink = stu[k].llink;
stu[stu[i].llink].rlink = i;
stu[i].rlink = k;
stu[k].llink = i;
} else {
stu[i].rlink = stu[k].rlink;
stu[stu[i].rlink].llink = i;
stu[i].llink = k;
stu[k].rlink = i;
}
}
- 删除:如图,1、2、3 号同学正在排队,删除 2 号同学,只需要将 1 号同学的后继改为 3 号同学,将 3 号同学的前驱改为 1 号同学。

int Del[100005]; // 记录 i 号同学是否已被删除
void del(int x) {
if (Del[x]) return; // 如果已被删除,则忽略这条指令
Del[x] = 1;
if (head == x) head = stu[x].rlink; // 特判删掉队首的情况
stu[stu[x].llink].rlink = stu[x].rlink;
stu[stu[x].rlink].llink = stu[x].llink;
}
AC 代码
#include <iostream>
using namespace std;
struct list {
int llink, rlink;
};
list stu[100005];
int n, m, head = 1;
int Del[100005];
void ins(int i, int k, int p) {
if (p == 0) {
if (head == k) head = i;
stu[i].llink = stu[k].llink;
stu[stu[i].llink].rlink = i;
stu[i].rlink = k;
stu[k].llink = i;
} else {
stu[i].rlink = stu[k].rlink;
stu[stu[i].rlink].llink = i;
stu[i].llink = k;
stu[k].rlink = i;
}
}
void del(int x) {
if (Del[x]) return;
Del[x] = 1;
if (head == x) head = stu[x].rlink;
stu[stu[x].llink].rlink = stu[x].rlink;
stu[stu[x].rlink].llink = stu[x].llink;
}
int main() {
cin >> n;
for (int i = 2; i <= n; i++) {
int k, p;
cin >> k >> p;
ins(i, k, p);
}
cin >> m;
while (m--) {
int x;
cin >> x;
del(x);
}
for (int i = head; i != 0; i = stu[i].rlink) {
cout << i << " ";
}
return 0;
}
代码的简略写法
可以将 0 号同学作为虚拟队首,这样就无需特判队首的情况了。
#include <iostream>
using namespace std;
struct list {
int llink, rlink;
};
list stu[100005];
int n, m, head = 1;
int Del[100005];
void ins(int i, int k, int p) {
if (p == 0) {
stu[i].llink = stu[k].llink;
stu[stu[i].llink].rlink = i;
stu[i].rlink = k;
stu[k].llink = i;
} else {
stu[i].rlink = stu[k].rlink;
stu[stu[i].rlink].llink = i;
stu[i].llink = k;
stu[k].rlink = i;
}
}
void del(int x) {
if (Del[x]) return;
Del[x] = 1;
stu[stu[x].llink].rlink = stu[x].rlink;
stu[stu[x].rlink].llink = stu[x].llink;
}
int main() {
cin >> n;
stu[0].rlink = 1; // 用 stu[0].rlink 作为队首
for (int i = 2; i <= n; i++) {
int k, p;
cin >> k >> p;
ins(i, k, p);
}
cin >> m;
while (m--) {
int x;
cin >> x;
del(x);
}
for (int i = stu[0].rlink; i != 0; i = stu[i].rlink) {
cout << i << " ";
}
return 0;
}
另一种做法
C++ STL 同样封装好了链表以简化操作(需要 #include<list> ),下面是一些常见操作:
-
list<int> a:定义一个 int 类型的链表 a。 -
list<int>::iterator it;:定义一个名为 it 的迭代器。 -
a.begin(); a.end():链表开始和末尾的迭代器(左闭右开)。 -
it++; it--;:迭代器指向下一个和上一个元素。 -
a.push_back(x):在链表末尾插入元素 x。 -
a.insert(it, x):在迭代器 it 前插入元素 x。 -
a.erase(it):删除迭代器 it 所在元素。
设置一个迭代器数组 pos[i] 指向 i 号同学在链表中的位置。
AC 代码
#include <iostream>
#include <list>
#include <iterator>
using namespace std;
list<int> a;
int n, m, del[100005];
list<int>::iterator pos[100005]; // pos[i] 为指向 i 号同学在链表 a 中位置的迭代器
int main() {
cin >> n;
a.push_back(1);
pos[1] = a.begin();
for (int i = 2; i <= n; i++) {
int k, p;
cin >> k >> p;
if (p == 0) {
pos[i] = s.insert(pos[k], i);
} else {
list<int>::iterator it = pos[k]; // 类型也可以写为 auto
it++; // 注意:因为 insert 函数是在迭代器前插入元素,所以要 it++
pos[i] = s.insert(it, i);
}
}
cin >> m;
while (m--) {
int x;
cin >> x;
if (!del[x]) {
s.erase(pos[x]);
del[x] = 1;
}
}
for (int x : a) { // 使用变量 x 遍历链表 a,需要 C++11 以上
cout << x << " ";
}
return 0;
}

浙公网安备 33010602011771号