https://codeforces.com/contest/1833/problem/D
选择一个区间 [l,r](1≤l≤r≤n),将这个区间内的数翻转。 将原来排列中 [r+1,n] 和 [1,−1] 的位置互换。即将排列分为两段,前面一段是 [r+1,n],后面一段是 [1,−1]。
请你输出通过上述操作能够得到的字典序最大的排列。
解题思路
首先考虑如何得到字典序最大的排列。我们很容易想到,对于每个位置 i,我们应该把它和后面最大的比它大的数交换,这样可以保证得到的排列字典序最大。
那么问题就转化成了如何找到后面最大的比当前位置大的数。可以使用单调栈实现,维护一个单调递减的栈,遇到比栈顶元素大的数就弹出栈顶并记录答案。
接下来考虑如何利用上述操作将排列变成字典序最大的。
首先,我们可以发现对于一个区间 [l,r],如果将其翻转,再把前后两部分交换,相当于把 pi 和 pr 交换了位置,同时把 +1…−1pl+1…r−1 翻转。
我们可以发现,如果要让某个 pi 能够到达最右侧的位置,那么在翻转后一定要满足 i∈[l,r]。因此我们考虑对于每个 i,找到最靠右的 j,使得 i∈[j,n]。
找到 j 后,我们可以将 [j,i] 这个区间翻转,然后将 [i+1,n] 和 [1,j−1] 两个区间交换。这样就可以保证操作后 i 会被移动到最右侧,且不会影响到其他元素的位置关系。
因为我们要求字典序最大,所以每次找到最靠右的 j 后,应该让 pj 和 pi 交换位置。这样就可以一步步将所有比后面元素小的元素移到最右侧,最终得到字典序最大的排列。
#include <iostream>
#include <vector>
#include <stack>
using namespace std;
vector<int> getLexicographicallyMaxPermutation(vector<int>& p) {
int n = p.size();
vector<int> result(n); // 存储结果的数组
stack<int> st; // 单调递减栈,存储后面最大的比当前位置大的数
for (int i = n - 1; i >= 0; i--) {
while (!st.empty() && st.top() < p[i]) {
st.pop();
}
if (!st.empty()) { // 找到了比当前位置大的数
result[i] = st.top();
} else {
result[i] = 0; // 没有找到,则置为0
}
st.push(p[i]);
}
return result;
}
int main() {
int t;
cin >> t; // 读取测试用例数量
while (t--) {
int n;
cin >> n; // 读取排列长度
vector<int> p(n);
for (int i = 0; i < n; i++) {
cin >> p[i]; // 读取排列元素
}
vector<int> result = getLexicographicallyMaxPermutation(p);
for (int i = 0; i < n; i++) {
cout << result[i] << " "; // 输出结果
}
cout << endl;
}
return 0;
}
有比后面元素小的元素移到最右侧,最终得到字典序最大的排列