传送门
给出 n n n个数,仅包含 [ 1 , k ] [1,k] [1,k]中的数,且至少含有 k k k个不同的数。找到一个长度为 k k k的子序列是 k k k的一个排列且字典序最小。
这个题算是比较难想的贪心了,而且要用栈维护。一开始用尺取发现维护不了,贪心的话自己又陷入了如下几个误区:
正解:
从前向后维护序列,对于未入栈的数,在它入栈之前检查栈顶的元素是否会在后面出现而且大于它,不断检查直到栈顶元素不符合条件。其中后缀的元素是否出现只需要记录数目然后扫描的过程中不断减少即可。
解释:(参考了队友visors)
我们的解一定是在一个子区间中选出来的,那么当我们的区间向右扩大时,若该数字出现过,不用讨论,因为该数字即使在该处取,也没有在之前取到的地方取好(后面能接的数是之前的子集);若该数字没出现过,那么我们要考虑该数字能否在追求字典序最小的情况下优化当前解。根据字典序,当然是越小的数越出现在前面越好,所以我们可以对当前解从后往前检查,如果该数字比后端数字小,那么将它放在该位置显然会使字典序最小,但放的时候也要考虑后面会不会有机会再把这个后端元素取回来,如果取不回来,解最终就凑不齐 k k k 个了。当我们上浮至无法上浮的位置时,就接着去考虑下一个数的加入就行了。因为我们每次上浮都保证了去掉的后端元素存在于后面未取数字中,所以一次线性扫描一定能恰巧取出字典序最小的 k k k 个元素形成的序列。
#include <bits/stdc++.h>
using namespace std;
const int maxn = 2e5 + 10;
int a[maxn], cnt[maxn];
bitset<maxn> vis;
vector<int> q;
int main() {
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int n, k;
cin >> n >> k;
for (int i = 1; i <= n; i++) {
cin >> a[i];
cnt[a[i]]++;
}
for (int i = 1; i <= n; i++) {
cnt[a[i]]--;
if (!vis[a[i]]) {
while (!q.empty() && cnt[q.back()] && q.back() > a[i]) {
vis[q.back()] = 0, q.pop_back();
}
q.push_back(a[i]);
vis[a[i]] = 1;
}
}
bool flag = 1;
for (auto i : q) {
if (flag)
flag = 0, cout << i;
else
cout << " " << i;
}
cout << endl;
return 0;
}