题解:LGP11396 排队
PS:虽然不是很难,但个人认为这题涉及的贪心思路挺好的。
题目描述
给定一个 \(1\) 到 \(n\) 的排列 \(p\),\((n \le 3\times 10^5)\),要求把这个排列划分成若干子串,首先每一串内部升序排序,然后所有串之间以最小的数为关键字升序排序。要求确定划分方案,使得最终排列字典序最大。
思路
首先,显然的贪心思路是,尽量把大的数跟着小的数放一起,带到前面去。
然后我赛时一开始想的是,从大到小枚举每个数 \(p_i\),然后确定 \(p_i\) 和它周围的谁匹配。但是发现这样不行。首先,随着 \(i\) 往两边扩展,\(p_i\) 在最终序列的位置不是单调的;其次,由于最靠前的一定是 \(1\),那么为了使字典序最大,我们应该从最前面开始考虑,而不是最大的数。
于是从小到大考虑每个数 \(p_i\),维护两个指针 \(l=i-1\),\(r=i+1\),维护一个最大值 \( \max = p_i\)。每次往 \(> \max\) 的那个方向扩展,如果两边都可以,那就选择最大的那边,然后更新 \(\max\)。
如果不往 \(>\max\) 的方向扩展,就会出现插队的情况,使答案更劣;若选择的是 \(l\) 和 \(r\) 较小的那边,那么可以也会更劣。因此上面的操作一定是最优的。
代码
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
using ull = unsigned long long;
const int N = 3e5 + 5;
int n, a[N], pos[N];
int vis[N];
vector<int> ans;
int main() {
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i];
pos[a[i]] = i;
}
int cnt = 0, mx, l, r;
for (int i = 1; i <= n; i++) {
if (vis[pos[i]]) continue;
ans.clear();
mx = i;
ans.push_back(i);
l = pos[i] - 1, r = pos[i] + 1;
vis[pos[i]] = 1;
while (true) {
if (l >= 1 && r <= n && !vis[l] && !vis[r] && a[l] > mx && a[r] > mx) {
if (a[l] > a[r]) {
ans.push_back(a[l]);
mx = a[l];
vis[l--] = 1;
} else {
ans.push_back(a[r]);
mx = a[r];
vis[r++] = 1;
}
} else if (l >= 1 && !vis[l] && a[l] > mx) {
ans.push_back(a[l]);
mx = a[l];
vis[l--] = 1;
} else if (r <= n && !vis[r] && a[r] > mx) {
ans.push_back(a[r]);
mx = a[r];
vis[r++] = 1;
} else break;
}
sort(ans.begin(), ans.end());
for (int j : ans) cout << j << ' ';
}
cout << '\n';
return 0;
}

浙公网安备 33010602011771号