题解:P5156 [USACO18DEC] Sort It Out P
posted on 2025-03-21 03:39:51 | under | source
题意:给一个排列 \(a_1\dots a_n\),定义其中一个子集是合法的,当且仅当可以对子集内元素进行若干次操作,使得排列有序。对 \(a_i=x\) 操作,先将其拎出来然后从左往右比较直到遇见比它大元素,然后插入。求字典序第 \(k\) 小的大小最小合法子集。\(n\le 10^5,k\le 10^{18}\)。
合法的必要条件是剩下元素有序(因为无法改变它们内部顺序),容易发现这是充要的。貌似不好比较因为还要排序,注意到剩下元素的有序性,考虑通过它来比较。发现两个子集 \(A<B\) 当且仅当 \(\complement A>\complement B\)。
求第 \(k\) 大的最长上升子序列是容易的,预处理一下每个起点的最长上升子序列长度和方案。\(O(n\log n)\)。
代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N = 1e5 + 5;
int n, k, a[N], rk[N], lis;
struct node{int mx, cnt;} f[N];
bool vis[N];
vector<int> vc[N];
inline void ADD(node &A, node B){
if(A.mx < B.mx) A = {B.mx, 0};
if(A.mx == B.mx) A.cnt = min(A.cnt + B.cnt, k);
}
namespace BIT{
node t[N];
inline void upd(int a, node k) {for(; a < N; a += a & -a) ADD(t[a], k);}
inline node qry(int a) {node res = {0, 0}; for(; a > 0; a -= a & -a) ADD(res, t[a]); return res;}
}using namespace BIT;
signed main(){
cin >> n >> k;
for(int i = 1; i <= n; ++i) scanf("%lld", &a[i]), rk[a[i]] = i;
for(int i = n; i; --i){
f[i] = qry(n - a[i] + 1);
if(f[i].cnt == 0) f[i] = {1, 1};
else ++f[i].mx;
upd(n - a[i] + 1, f[i]);
lis = max(lis, f[i].mx);
vc[f[i].mx].push_back(a[i]);
}
for(int i = 1; i <= n; ++i) sort(vc[i].begin(), vc[i].end(), [](int a, int b){return a > b;});
printf("%lld\n", n - lis);
for(int len = lis, lst = 0; len; --len){
for(auto i : vc[len]){
if(lst > rk[i] || a[lst] > i) continue;
if(f[rk[i]].cnt >= k){
lst = rk[i], vis[i] = true;
break;
}
else k -= f[rk[i]].cnt;
}
}
for(int i = 1; i <= n; ++i) if(!vis[i]) printf("%lld\n", i);
return 0;
}

浙公网安备 33010602011771号