Perfect Flush(栈,贪心)

文章描述了一种解决给定数组中找到字典序最小的包含1到m的所有整数的子序列的方法。通过使用栈数据结构,从前往后遍历数组,每次判断栈顶元素是否可以被更小的元素替换,从而保证字典序最小。代码实现了一个高效的O(n)解决方案。
摘要由CSDN通过智能技术生成

https://codeforces.com/gym/102433

题意
给定一个长度为 n n n 的数组 a [ ] a[] a[],整数 m m m
保证对于 1 ~ m 中的所有数都会出现在这个数组中至少一次。

找到字典序最小的子序列,使得 1 ~ m 中的所有数出现且仅出现一次。

1 ≤ k ≤ n ≤ 200000 1 ≤ k ≤ n ≤ 200 000 1kn200000
1 ≤ x i ≤ k . 1 ≤ x_i ≤ k. 1xik.

思路
从前往后对于每个位置来说,假设当前位置 i 拿了放在子序列中,需要判断之前拿过的数:

  • 如果之前拿过比 a[i] 大的数 x,并且 x 在后面的位置也出现,为了使得所拿数的字典序尽可能小,那么之前拿的 x 就可以丢掉,到后面的时候再拿;
  • 如果之前拿过比 a[i] 大的数 x,但是 x 在后面的位置没有出现,那么 x 必拿不可。

但是如果对于每个位置都遍历之前拿过的数,复杂度是 O(n^2),需要优化。


对于上一位置 i 判断了之前拿过的数,那么当前位置 j

  • 需要向前依次判断拿过的数,但可以在下面的条件下停止
    1. 碰见一个比 a[j] 小的数 x(前面的数已经被 x 判断过了,不用再判断),或者
    2. 碰见一个比 a[j] 大的数 x,但是 x 在后面的位置没有出现,这个 x 必拿。(此时 x 前面拿的数 要么大于 x,已经被 x 判断过了不用判断;要么小于 x,但如果丢掉之后 x 必将前移,使得子序列字典序变大,那么都不能丢,都不用判断。所以这种情况下停止往前判断)
  • 然后拿上当前元素。(每次都把当前位置拿上,不优的话后面判断时会丢)

在这种判断方式下,所有拿过的数只会被判断一次,O(n),并且保证了所拿子序列字典序最小。

可以用一个栈来实现上述判断,栈中依次存放要拿的子序列中的数。
遍历所有的位置,对于每个位置:

  • 如果当前元素 x 在前面已经拿过,跳过;
  • 依次向前判断拿过的数,如果栈顶元素大于等于当前元素,并且栈顶元素在后面的位置还出现,那么就可以丢掉栈顶元素;
  • 拿上当前元素,把当前元素入栈;
for(int i=1;i<=n;i++)
{
	int x = a[i];
	if(mp[x]) continue;
	
	while(stk.size() && a[stk.top()] >= x && last[a[stk.top()]] > i) mp[a[stk.top()]] = 0, stk.pop();
	
	stk.push(i);
	mp[a[i]] = 1;
}

最终栈中元素就是所选子序列元素。

Code

//https://codeforces.com/gym/102433
#include<bits/stdc++.h>
using namespace std;

#define Ios ios::sync_with_stdio(false),cin.tie(0)
#define endl '\n'
map<int, int> mp;

const int N = 200010, mod = 1e9+7;
int T, n, m;
int a[N], last[N];
int flag[N];

signed main(){
	Ios;
	cin >> n >> m;
	for(int i=1;i<=n;i++) cin >> a[i], last[a[i]] = i;
	
	stack<int> stk;
	for(int i=1;i<=n;i++)
	{
		int x = a[i];
		if(mp[x]) continue;
		
		while(stk.size() && a[stk.top()] >= x && last[a[stk.top()]] > i) mp[a[stk.top()]] = 0, stk.pop();
		stk.push(i);
		mp[a[i]] = 1;
	}
	vector<int> ans;
	while(stk.size()) ans.push_back(stk.top()), stk.pop();
	for(int i=ans.size()-1;i>=0;i--) cout << a[ans[i]] << " ";
	
	return 0;
}

代码很简单,但是想清楚思路还是要花点时间的。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值