题解:LGP11396 排队

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;
}
posted @ 2025-02-03 14:01  chenwenmo  阅读(6)  评论(0)    收藏  举报