队列
一、队列
1. 特点
队列是一种数据结构,它按照先进先出(First-In-First-Out,FIFO)的原则存储和访问数据。它类似于现实生活中排队的概念,最先进入队列的元素将最先被访问和删除,而最后进入队列的元素将最后被访问和删除。
2. 数组 / STL
2.1 存储
数组
int q[1010];
int head = 1, tail = 1;
STL
#include <queue>
queue <int> q;
2.2 操作
数组 | STL | |
---|---|---|
插入 | q[tail++] = x; | q.push(x); |
删除 | head++; | q.pop(); |
判空 | head == tail | q.empty() |
队首 | q[head] | q.front() |
队尾 | q[tail] | q.back() |
大小 | tail-head | q.size() |
3. 优先队列
3.1 存储
#include <queue>
priority_queue <int> q; // 越大优先级越高
priority_queue <int, vector<int>, greater<int> > q; // 越小优先级越高
3.2 操作
操作 | 程序 |
---|---|
插入 | q.push(x) |
删除 | q.pop() |
队首 | q.top() |
二、队列模板
1. 时间最近问题
这是很典型的一道时间最近问题,用一个队列可以保存离当前最近可能满足要求的事物。这个时候,当队头指向的元素超出时间范围,我们就认为其永远无法满足要求,然后踢出队列。
对于求出不同的国籍数量,我们可以用下面几种方法:
- 桶
程序:a[x]++; if (a[x] == 1) k++;
特点:支持动态运算
复杂度: O ( n ) O(n) O(n) - 排列
程序:if (a[i] != a[i-1] k++;
特点:略
时间复杂度: O ( n log n ) O(n \log n) O(nlogn) - 映射
程序:略
特点:略
时间复杂度: O ( n log n ) O(n \log n) O(nlogn)
参考程序如下:
#include <iostream>
using namespace std;
struct Node {
int x, t;
} q[300500];
int head = 1, tail = 1;
int n, ans;
int t, k, x;
int cnt[100100];
int main() {
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> t >> k;
for (int j = 1; j <= k; j++) {
cin >> x;
cnt[x]++;
if (cnt[x] == 1) ans++;
q[tail++] = {x, t};
}
while (head < tail) {
if (q[head].t + 86400 <= t) {
cnt[q[head].x]--;
if (cnt[q[head].x] == 0) ans--;
head++;
} else {
break;
}
}
cout << ans << endl;
}
return 0;
}
2. 队列模拟问题
这就是一道典型的队列模拟题。易错点主要有:
tail
指向待放入元素continue
不要写成break
- 每轮记得更新
flag = false
参考程序如下:
#include <iostream>
#include <cstdio>
using namespace std;
int cnt;
bool flag;
int m, n, x;
int q[1010];
int head = 1, tail = 1;
int main() {
cin >> m >> n;
for (int i = 1; i <= n; i++) {
cin >> x;
flag = true;
for (int j = head; j < tail; j++) {
if (q[j] == x) {
flag = false;
break;
}
}
if (!flag) continue;
if (tail-head >= m) head++;
if (flag) q[tail++] = x, cnt++;
}
cout << cnt;
return 0;
}
3. 任务调度问题
这是一道典型的任务调度问题。参考代码如下:
#include <iostream>
using namespace std;
const int N = 1e5+10;
int n, cnt;
int a[N], b[N], c[N];
int q[N], head = 1, tail = 1;
int vis[N], id[N], t1[N], t2[N];
int main() {
cin >> n;
for (int i = 1; i <= n; i++) cin >> a[i], id[a[i]] = i;
for (int i = 1; i <= n; i++) cin >> b[i], t1[b[i]]++;
for (int i = 1; i <= n; i++) cin >> c[i], t2[c[i]]++;
for (int i = 1; i <= n; i++)
if (t1[i] == 0 || t2[i] == 0) q[tail++] = id[i];
while (head < tail) {
int col = q[head++];
if (vis[col]) continue;
vis[col] = 1;
t1[b[col]]--;
t2[c[col]]--;
if (t1[b[col]] == 0) q[tail++] = id[b[col]];
if (t2[c[col]] == 0) q[tail++] = id[c[col]];
cnt++;
}
cout << cnt;
return 0;
}
三、优先队列模板
1. 贪心+优先队列
参考程序如下:
#include <iostream>
#include <cstdio>
#include <queue>
using namespace std;
int n, x, ans;
priority_queue <int, vector<int>, greater<int> > q;
int main() {
freopen("fruit.in", "r", stdin);
freopen("fruit.out", "w", stdout);
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> x;
q.push(x);
}
while (q.size() > 1) {
int a = q.top();
q.pop();
int b = q.top();
q.pop();
int c = a + b;
ans += c;
q.push(c);
}
cout << ans;
fclose(stdin);
fclose(stdout);
return 0;
}
2. 序列+优先队列
#include <iostream>
#include <string>
#include <queue>
using namespace std;
string s;
int k, m, cnt;
priority_queue <int, vector<int>, greater<int> > q;
int main() {
cin >> k >> m;
q.push(1);
for (int i = 1; i <= k; ++i) {
int x = q.top();
s += to_string(x);
q.pop();
q.push(2*x+1);
q.push(4*x+5);
}
cout << s << endl;
while (cnt < m) {
for (int i = 0; i < s.size()-1; i++) {
if (s[i] < s[i+1]) {
cnt++;
s.erase(i, 1);
break;
}
}
}
cout << s;
return 0;
}
四、前缀和/区间和/差分
前缀和公式:
s
i
=
s
i
−
1
+
a
i
s_i=s_{i-1}+a_i
si=si−1+ai
区间和公式:
s
r
−
s
l
−
1
=
a
l
+
⋯
+
a
r
s_r-s_{l-1}=a_l+\cdots+a_r
sr−sl−1=al+⋯+ar
差分公式:
b
i
=
b
i
−
b
i
−
1
b_i=b_i-b_{i-1}
bi=bi−bi−1
区间修改公式:
a
l
⋯
a
r
=
a
l
⋯
a
r
+
c
b
l
=
b
l
+
c
b
r
+
1
=
b
r
+
1
−
c
a_l \cdots a_r = a_l \cdots a_r + c \\ b_l=b_l+c \\ b_{r+1}=b_{r+1}-c
al⋯ar=al⋯ar+cbl=bl+cbr+1=br+1−c
例题
1. [CSP-J 2019 普及组] 公交换乘
这是很变形的一道时间最近问题。
#include <iostream>
using namespace std;
struct Node {
int p, t;
bool used;
} q[100100];
bool flag;
int n, ans;
int o, P, T;
int head = 1, tail = 1;
int main() {
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> o >> P >> T;
if (o == 0) {
ans += P;
q[tail++] = {P, T+45, false};
} else {
flag = false;
while (head < tail && q[head].t < T) head++;
for (int j = head; j < tail; j++) {
if (q[j].p >= P && q[j].used == false) {
flag = true;
q[j].used = true;
break;
}
}
if (!flag) ans += P;
}
}
cout << ans;
return 0;
}
2. 序列合并
#include <iostream>
#include <queue>
using namespace std;
int n;
int id[100100];
int a[100100], b[100100];
typedef pair <int, int> pii;
priority_queue <pii, vector<pii>, greater<pii> >q;
int main() {
cin >> n;
for (int i = 1; i <= n; i++) cin >> a[i];
for (int i = 1; i <= n; i++) {
cin >> b[i];
q.push(pii(b[i]+a[1], i));
id[i] = 1;
}
for (int i = 1; i <= n; i++) {
cout << q.top().first << " ";
int idx = q.top().second;
q.pop();
id[idx]++;
q.push(pii(b[idx]+a[id[idx]], idx));
}
return 0;
}
附录
难点:数组模拟队列的大小
实在不知道开多大,就找到数据范围中最大的那个值来开。