洛谷P1638 逛画展 题解 尺取法/双指针/队列
题目链接:https://www.luogu.com.cn/problem/P1638
题目大意:
给你一个长度为 \(n (\le 10^6)\) 的数组,数组中每个元素的范围在 \(1\) 至 \(m\) 之间(\(1 \le m \le 2000\)),
求一个最短的连续子序列包含 \(1\) 到 \(m\) 内的所有元素。
解题思路:
这道题目我的解法是开一个队列,队列里面的元素是连续的。
那么怎么保证这个队列是连续的呢?
我只需要从 1 到 n 将每个元素入队,然后在需要的时候讲队首元素出队列。
但是不能单纯地入队出队。
我们开一个数组 \(c[i]\) 来表示第 \(i\) 位玩画家在队列中出现的次数,开一个变量 \(cnt\) 用于统计当前队列中有多少个不同的画家。
然后我们从 1 到 n 遍历 i ,对于每个 i,我们在令 \(a_i\) 入队的同时令 \(c[a_i] ++\) ,
此时若 \(c[a_i]\) 刚刚变为1,则说明编号为 \(a_i\) 的画家是第一次入队的,则我们令 \(cnt ++\) ,若此时 \(cnt\) 还没有等于 \(m\) ,说明还没有凑齐 \(m\) 个画家;
若此时 \(cnt == m\) 了,说明以第 \(i\) 个画家结尾的连续 que.size()
幅画凑成了 m 个画家。
但是这个时候并不一定是最优的结果,此时我们还要去判断:
如果当前队首元素对应的画家的画(假设为 \(a_j\) )出现的次数 \(\lt 1\) ,说明以 \(a_i\) 结尾的画去掉队列最前面的那副画也是能凑够 m 个画家的,那么我们就可以去掉队首的元素。
我们一直循环这样的操作指导队首元素不能出队列为止(即队首元素 \(a_j\) 对应的 \(c[a_j] == 0\) ,此时不能去掉它),此时的 que.size()
就是我们的一个备选答案。
我们的目的就是寻找所有备选答案中的最小值。
在这里我是使用队列queue来实现这个功能的。
我们也可以开两个指针(坐标)来解决这个问题,开两个指针的方法在国内被叫做尺取法,在国外被叫做“two points”,所以我就直接翻译成“双指针”了。
队列方式实现代码如下:
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1000010, maxm = 2020;
int m, c[maxm], cnt, ans = INT_MAX, n, a[maxn], ans_a, ans_b;
queue<int> que;
int main() {
cin >> n >> m;
for (int i = 1; i <= n; i ++) cin >> a[i];
for (int i = 1; i <= n; i ++) {
que.push(i);
c[ a[i] ] ++;
if (c[ a[i] ] == 1) cnt ++;
while (!que.empty() && c[ a[que.front()] ] > 1) {
c[ a[que.front()] ] --;
que.pop();
}
if (cnt == m && que.size() < ans) {
ans = que.size();
ans_a = que.front();
ans_b = que.back();
}
}
cout << ans_a << " " << ans_b << endl;
return 0;
}