1. 程式人生 > 其它 >2022.3.22 程式碼源每日一題div1 #608. 字典序最小

2022.3.22 程式碼源每日一題div1 #608. 字典序最小


首先考慮到選擇數字應該從第一個數字向後逐個確定
那麼每一個數字的可選範圍是哪些呢?
比如開始的時候,我們要選擇\(n\)個數,那我們應該保證第一個選擇之後,第一個數對應位置的右邊還有\(n-1\)個與已選數字不同的數字
\(cnt[a[i]]\)表示每個\(a[i]\)出現的次數
首先,我們要保證每次選的數字與已選數字不同,這個問題我們可以每次選擇數字\(a[i]\)之後把對應的\(cnt[a[i]]\)賦為\(0\),這樣在以後就必然不會將\(a[i]\)加入單調佇列,也就不會再選擇這個數字了
其次我們考慮每次向右拓展的最遠位置是哪裡,由於每次選擇數字我們都會將一個數字的\(cnt\)值設為\(0\)

,而且每次選擇之後,選擇位置的右邊剩下序列中存在的與已選擇數字不同的數字種類只能減少一個,也就是減少一次的機會已經被我們選擇的這個數給用掉了,那麼每次我們只能向右拓展直到當前位數字的\(cnt\)值為\(0\)的前一個位置。
最後考慮如何維護待選集合裡的最小值,這裡我們可以用一個單調遞增的單調佇列來維護,隊頭維護的就是最小值,因此每次只需從隊頭取即可。

道理是這麼個道理,但實現一下細節太多,把上述思路轉換一下,迴圈\(1-m\),每次維護優先佇列把大於等於\(a[i]\)的都彈掉,加入當前數字,如果當前數字\(cnt\)減為零了,就把佇列裡所有小於等於當前值的數以及當前值都加入答案,為什麼呢?因為題目裡滿足\(a[i]\)

中必然會出現按\(1-n\)的每一個數,也就是說我們必須要選擇\(1-n\)中的每一個數,那當前值的\(cnt\)都要變為零了我們還不選,以後就沒得選了,所以把佇列裡比當前值小的都加進入,再把當前值加入

#include<algorithm>
#include<cstdio>
#include<iostream>
#include<cstring>
#include<vector>
#include<cmath>
#include<queue>
#include<set>
#define x first
#define y second
#define ll long long
#define pii pair <int, int>
#define mp(a, b) make_pair(a, b)
#define rep(i, a, b) for(int i = a; i <= b; ++i)
#define per(i, a, b) for(int i = a; i >= b; --i)
#define clr(a, b) memset((a),b,sizeof(a))
using namespace std;
inline int read(){
	int s = 0, w = 1; char ch = getchar();
	while(ch < '0' || ch > '9')   { if(ch == '-') w = -1; ch = getchar(); }
	while(ch >= '0' && ch <= '9') { s = (s << 3) + (s << 1) + (ch ^ 48); ch = getchar(); }
	return s * w;
}
const int N = 1e6 + 50;
int n, m, num, ans[N], a[N], cnt[N], vis[N];
deque <int> q;
int main() {
	m = read(); n = read();
	rep(i, 1, m) {
		a[i] = read();
		cnt[a[i]] ++;
	}
	
	rep(i, 1, m) {
		if(cnt[a[i]] == 0) continue;
		cnt[a[i]] --;
		while(vis[a[i]] == 0 && q.size() && q.back() >= a[i]) {
			vis[q.back()] = 0;
			q.pop_back();
		} 
		if(vis[a[i]] == 0) {
			q.push_back(a[i]);
			vis[a[i]] = 1;
		}
		if(cnt[a[i]] == 0) {
			while(q.size() && q.front() <= a[i]) {
				int x = q.front();
				q.pop_front();
				ans[++ num] = x;
				cnt[x] = 0;
			}
		}
	}
	rep(i, 1, num) {
		if(i != 1) printf(" ");
		printf("%d", ans[i]);	
	}
	return 0;
}

另一種單調棧的寫法

注意每次彈出元素需要保證後面還有這個元素,若後面沒有這個元素我們又把他彈出了,以後就再也沒得選了,而這個題要求我們\(1-n\)的每個數都要出現一次,就不滿足題目要求了

#include<algorithm>
#include<cstdio>
#include<iostream>
#include<cstring>
#include<vector>
#include<cmath>
#include<queue>
#include<stack>
#include<set>
#define x first
#define y second
#define ll long long
#define pii pair <int, int>
#define mp(a, b) make_pair(a, b)
#define rep(i, a, b) for(int i = a; i <= b; ++i)
#define per(i, a, b) for(int i = a; i >= b; --i)
#define clr(a, b) memset((a),b,sizeof(a))
using namespace std;
inline int read(){
	int s = 0, w = 1; char ch = getchar();
	while(ch < '0' || ch > '9')   { if(ch == '-') w = -1; ch = getchar(); }
	while(ch >= '0' && ch <= '9') { s = (s << 3) + (s << 1) + (ch ^ 48); ch = getchar(); }
	return s * w;
}
const int N = 1e6 + 50;
int n, k, a[N], vis[N], lst[N];
int main() {
	n = read(); k = read();
	rep(i, 1, n) {
		a[i] = read();
		lst[a[i]] = i;
	}
	stack <int> stk;
	rep(i, 1, n) {
		if(vis[a[i]]) continue;
		while(stk.size() && stk.top() > a[i] && lst[stk.top()] > i) {
			vis[stk.top()] = 0;
			stk.pop();	
		}
		stk.push(a[i]);
		vis[a[i]] = 1;
	}
	
	vector <int> ans;
	while(stk.size()) 
		ans.push_back(stk.top()), stk.pop();
		
	reverse(ans.begin(), ans.end());
	rep(i, 0, ans.size() - 1) {
		if(i) printf(" ");
		printf("%d", ans[i]);
	}
	return 0;
}