初看本题毫无思路,只能从特殊的 \(K = 1\) 出发。

但是直接考虑构造一组字典序最小的方案还是不好构造,可以考虑先手玩一下样例。通过自己手玩的样例可以发现,貌似没有被选出来的数在原排列中都是递增的子序列。比如说 \(1 \ 6 \ 5 \ 3 \ 4 \ 2\) 可以证明 \(2 \ 5 \ 6\) 是字典序最小的方案,那么剩下的数按照在原排列中的顺序依次写下是 \(1 \ 3 \ 4\) 是一个递增序列。可以思考一下为什么会有这样的结论出现。

不难发现不论我们怎么操作,原来序列中顺序对的相对位置必然是不会变动的。比如说上方的 \(1 \ 3 \ 4\) 不论怎么变换 \(1\) 都会在 \(3\) 之前,\(3\) 都会在 \(4\) 之前。并且你会发现将剩下的所有数按照字典序依次进行操作是能让这个排列重新排好的。于是我们可以得到一个结论,原排列中的任意一个递增序列保留下来剩下的作为一个操作集合都能时这个排列重新排好。

既然如此,为了让操作集合字典序最小,相反我们需要让保留集合字典序最大。因此保留集合就一定需要选择最长递增子序列中字典序最大的那个。可以考虑每次贪心地选取能拼成最长递增子序列中的最大的元素即可,具体实现可以先求出 \(dp_i\) 表示以 \(i\) 位置结尾的最长递增子序列长度。最后将每个位置按照 \(dp\) 值为第一关键字,按照 \(a_i\) 为第二关键字排序即可。

那么回来思考原问题,字典序第 \(K\) 小的方案怎么求。不难发现实际上我们还是要求保留集合第 \(K\) 大的方案。于是可以考虑从低到高位逐步确定选择的集合,那么我们就需要求出 \(dp_i\) 表示以 \(i\) 开头的最长递增子序列的数量。可以先考虑一个朴素的求法,令 \(f_i\) 为以 \(i\) 开头的最长递增子序列的长度,那么会有转移:

\[dp_i = \sum\limits_{f_i = f_j + 1, a_i < a_j, i < j} dp_j
\]

实际上又因为 \(f_i = \max\limits_{a_i < a_j, i < j} f_j + 1\),实际上我们的 \(dp\) 就是求权值在一段后缀中 \(f\) 最大值的数量,这个可以直接在权值线段树上实现,于是我们就将求 \(dp_i\) 的复杂度降至 \(O(n \log n)\)。

那么最后确定第 \(K\) 大的保留集合时,将所有 \(dp\) 值相同的位置压入到一个 \(vector\) 当中并按照权值为第二关键字排序,依次确定每个位即可。

需要注意的是 \(dp_i\) 的数量可能会爆 \(long long\) 但因为我们只需要和 \(K\) 比大小所以需要随时将 \(dp\) 值与 \(1e18\) 取 \(\min\)。

#include <bits/stdc++.h>
using namespace std;
#define int long long
#define ls (p << 1)
#define rs (p << 1 | 1)
#define mid (l + r >> 1)
#define rep(i, l, r) for (int i = l; i <= r; ++i)
#define dep(i, l, r) for (int i = r; i >= l; --i)
const int N = 100000 + 5;
const int inf = 1000000000000000001;
struct tree {
int mx, cnt;
}dp[N], t[N << 2];
int n, k, len, ans, a[N], book[N];
vector <int> G[N];
int read() {
char c; int x = 0, f = 1;
c = getchar();
while (c > '9' || c < '0') { if(c == '-') f = -1; c = getchar();}
while (c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar();
return x * f;
}
tree up(tree x, tree y) {
tree ans;
if(x.mx > y.mx) ans = x;
else if(x.mx < y.mx) ans = y;
else ans.mx = x.mx, ans.cnt = min(x.cnt + y.cnt, inf);
return ans;
}
void update(int p, int l, int r, int x, int y, tree k) {
if(l >= x && r <= y) { t[p] = k; return;}
if(mid >= x) update(ls, l, mid, x, y, k);
if(mid < y) update(rs, mid + 1, r, x, y, k);
t[p] = up(t[ls], t[rs]);
}
tree query(int p, int l, int r, int x, int y) {
if(l >= x && r <= y) return t[p];
tree ans; ans.mx = ans.cnt = 0;
if(mid >= x) ans = up(ans, query(ls, l, mid, x, y));
if(mid < y) ans = up(ans, query(rs, mid + 1, r, x, y));
return ans;
}
bool cmp(int x, int y) {
return a[x] > a[y];
}
signed main() {
n = read(), k = read();
rep(i, 1, n) a[i] = read();
dep(i, 1, n) {
dp[i] = query(1, 1, n, a[i], n);
++dp[i].mx, dp[i].cnt = (dp[i].mx == 1 ? dp[i].cnt + 1 : dp[i].cnt);
update(1, 1, n, a[i], a[i], dp[i]);
G[dp[i].mx].push_back(i), len = max(len, dp[i].mx);
}
rep(i, 1, len) sort(G[i].begin(), G[i].end(), cmp);
int P = 0;
dep(i, 1, len) {
for (int j = 0; j < G[i].size(); ++j) if(G[i][j] > P && a[G[i][j]] > a[P]) {
if(dp[G[i][j]].cnt < k) k -= dp[G[i][j]].cnt;
else { P = G[i][j]; break;}
}
book[a[P]] = 1; if(P != n + 1 && !ans) ans = i;
}
printf("%lld\n", n - ans);
rep(i, 1, n) if(!book[i]) printf("%lld\n", i);
return 0;
}

值得一提的是,在这种最优性问题毫无思路时,可以通过手玩样例发现一些策略和性质。

[USACO18DEC]Sort It Out P的更多相关文章

  1. 题解-USACO18DEC Sort It Out

    Problem 洛谷5156 题意概要:给定一个长为\(n\)的排列,可以选择一个集合\(S\)使这个集合内部元素排到自己在整个序列中应该在的位置(即对于集合\(S\)内的每一个元素\(i\),使其排 ...

  2. 洛谷P5156 [USACO18DEC]Sort It Out

    这题就是让你求字典序第k小的最短乱序子序列 转换一下,其实就是字典序第k大的最长上升子序列 就统计一下以i结尾的最长上升子序列\(f[i]\),长度为i的上升子序列的开头组成的集合\(v[i]\),转 ...

  3. p5156 [USACO18DEC]Sort It Out

    传送门 分析 我们发现对于没有发现的点相对位置不会发生改变 于是我们可以吧问题转化为求一个lis 于是我们字典序第k小的答案就是字典序第k大的lis 代码 #include<iostream&g ...

  4. [USACO18DEC]Sort It Out(树状数组)

    [Luogu5156] 题解 求字典序第 k 小的满足题意的集合,取反一下,就是求序列中字典序第 k 大的最长上升子序列 [51nod1376] 最长递增子序列的数量 置 \(f_{i}\)表示以权值 ...

  5. [算法]——归并排序(Merge Sort)

    归并排序(Merge Sort)与快速排序思想类似:将待排序数据分成两部分,继续将两个子部分进行递归的归并排序:然后将已经有序的两个子部分进行合并,最终完成排序.其时间复杂度与快速排序均为O(nlog ...

  6. [算法]——快速排序(Quick Sort)

    顾名思义,快速排序(quick sort)速度十分快,时间复杂度为O(nlogn).虽然从此角度讲,也有很多排序算法如归并排序.堆排序甚至希尔排序等,都能达到如此快速,但是快速排序使用更加广泛,以至于 ...

  7. shell之sort命令

    1 sort的工作原理 sort将文件的每一行作为一个单位,相互比较,比较原则是从首字符向后,依次按ASCII码值进行比较,最后将他们按升序输出. [rocrocket@rocrocket progr ...

  8. 详细解说 STL 排序(Sort)

    0 前言: STL,为什么你必须掌握 对于程序员来说,数据结构是必修的一门课.从查找到排序,从链表到二叉树,几乎所有的算法和原理都需要理解,理解不了也要死记硬背下来.幸运的是这些理论都已经比较成熟,算 ...

  9. SQL Tuning 基础概述06 - 表的关联方式:Nested Loops Join,Merge Sort Join & Hash Join

    nested loops join(嵌套循环)   驱动表返回几条结果集,被驱动表访问多少次,有驱动顺序,无须排序,无任何限制. 驱动表限制条件有索引,被驱动表连接条件有索引. hints:use_n ...

随机推荐

  1. ORA-14450: 试图访问已经在使用的事务处理临时表

    需要对临时表动态添加列,经常碰到表在事务中被使用的情况,如果可以的话,可以现在只用临时表的时候先truncate,这样可以终止事务对当前临时表的占用. execute immediate('trunc ...

  2. 「算法笔记」期望 DP 入门

    一.数学期望 1. 由来 在 \(17\) 世纪,有一个赌徒向法国著名数学家帕斯卡挑战,给他出了一道题目:甲乙两个人赌博,他们两人获胜的机率相等,比赛规则是先胜三局者为赢家,一共进行五局,赢家可以获得 ...

  3. Java程序设计基础笔记 • 【第2章 变量与数据类型】

    全部章节   >>>> 本章目录 2.1 变量 2.1.1 变量的概念 2.1.2 变量的使用 2.1.3 实践练习 2.2 数据类型 2.2.1 数据类型的种类 2.2.2 ...

  4. HTML网页设计基础笔记 • 【第7章 盒子模型】

    全部章节   >>>> 本章目录 7.1 盒子模型原理 7.1.1 盒子模型概述 7.1.2 盒子的大小 7.1.3 盒子之间的关系 7.2 标准文档流 7.2.1 标准文档流 ...

  5. Java高级程序设计笔记 • 【第1章 IO流】

    全部章节   >>>> 本章目录 1.1 File类访问文件 1.1.1 File 类 1.1.2 File 类方法 1.1.3 实践练习 1.2 文件过滤器 1.2.1 Fi ...

  6. Redis缓存安装Version5.0.7

    1.说明 Redis是一个开源(BSD许可)的, 内存中的数据结构存储系统, 它可以用作数据库.缓存和消息中间件. 这里介绍在Linux下使用源码编译安装的方式. 2.下载 官方下载地址:https: ...

  7. Ranger开源流水线docker化实践案例

    1.背景 开发部门决定在Apache Ranger开源社区贡献代码,目标是个人国内排名Top1,世界排名Top2,并且在已经成为Ranger项目的Committer情况下,争取成为Ranger项目的P ...

  8. dokcer部署Redis哨兵模式

    架构图 哨兵的介绍 sentinel , 中文是哨兵. 哨兵是redis 集群架构中非常重要的一个组件,主要功能如下: (1)集群监控:负责监控reidis master 和slave 进程是否正常工 ...

  9. 第10组 Alpha冲刺 (5/6)

    1.1基本情况 ·队名:今晚不睡觉 ·组长博客:https://www.cnblogs.com/cpandbb/p/13996848.html ·作业博客:https://edu.cnblogs.co ...

  10. 聊聊同步、异步、阻塞、非阻塞以及IO模型

    前言 在使用Netty改造手写RPC框架的时候,需要给大家介绍一些相关的知识,这样很多东西大家就可以看明白了,手写RPC是一个支线任务,后续重点仍然是Kubernetes相关内容. 阻塞与非阻塞 同步 ...