SLOJ P2101 文翁点名
SLOJ P2101
题目描述
文翁班上有\(N\)个学生,每次上体育课他们会随机排列为一排,每个人有一个编号\(A_i\)(\(1\)~\(N\)的排列),现在文翁希望让所有人按编号升序排成一排,可是他不希望采取普通的排序方法来完成。他开创了一个有趣的排列方法,每一次他会点名一个学生,被点名的学生会确保他的顺序正确,当右边挨着他的人编号比他小,就和右边交换,然后当左边挨着他的人编号比他大他也会交换位置。这样,被点名的学生就完成了按他认为的按顺序排好(在他看来 左边的学生编号比他小,右边的学生编号比他大).
文翁需要选出一个学生编号子集,遍历这个子集,依次叫子集中每个人出列,重复下去直到所有人都排好序。如他选出子集\(\{1,3,6\}\),他就会先叫\(1\),再叫\(3\),再叫\(6\).如果这\(N\)个人还没有排好序,他会再次叫\(1,3,6\)。很明显这样的子集很多,现在文翁希望得到的子集最小且能完成排序,这样他觉得还不够,他希望得到满足条件的字典序第\(K\)小子集。
输入格式
输入的第一行包含一个整数\(N\)
第二行包含一个整数\(K(1≤K≤1018)\)。
第三行包含\(N\)个空格分隔的整数,表示从左到右学生的编号。
保证存在至少\(K\)个符合要求的子集。
输出格式
第一行输出最少需要选择多少学生数量
接下来若干行输出在所有可能子集中的字典序\(K\)小的集合编号(从小到大)
样例
输入数据 1
4 1
4 2 1 3
输出数据 1
2
1
4
开始的时候序列为4213 。在文翁叫编号为1的学生之后,序列变为1423 。在文翁叫编号为4的学生之后,序列变为1234 。在这个时候,序列已经完成了排序。
输入数据 2
6 1
6 2 4 3 1 5
输出数据 2
3
1
3
6
数据规模与约定
有20%数据\(N≤6\),并且\(K=1\)
另外有30%数据\(K=1\)
100%数据:\(1≤N≤105,1≤K≤1018\)
题解
序列上选出一段有序序列,再将剩下的元素重新排列即可获得整个有序序列。
因为子集最小,所以选出有序序列最长,即求最长上升子序列。
求完后枚举字典序即可。
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5+100;
#define int long long
class Fast_IO{
#define SIZE (1 << 23) // 8MB buffer
char in[SIZE], *p1 = in, *p2 = in;
inline char gc() {
if (p1 == p2) p2 = (p1 = in) + fread(in, 1, SIZE, stdin);
return p1 == p2 ? EOF : *p1++;
}
public:
inline int read() {
int k = 0;char c = gc();
while (c < '0' || c > '9') c = gc();
while (c >= '0' && c <= '9') k = (k << 1) + (k << 3) + (c ^ 48), c = gc();
return k;
}
}IO;
int n,k,num[N],now;
class BIT{
#define lowbit(x) (x&-x)
#define int long long
public:
struct LIS{
int len,cnt;
void operator +=(const LIS &b){
if(len<b.len) len = b.len,cnt = b.cnt;
else if(len==b.len) cnt += min(b.cnt,(int)1e18-cnt);
}
}dp[N],tree[N];
void update(int x,LIS v){
while(x){
tree[x] += v;
x -= lowbit(x);
}
}
LIS query(int x){
LIS ans = {0,1};
while(x<=n){
ans += tree[x];
x += lowbit(x);
}
return ans;
}
#undef lowbit
}BIT;
vector<int> vec[N];
bool vis[N];
signed main(){
n = IO.read(),k = IO.read();
for(int i=1;i<=n;i++) num[i] = IO.read();
for(int i=n;i;i--){
BIT.dp[i] = BIT.query(num[i]+1);
BIT.dp[i].len++;
vec[BIT.dp[i].len].push_back(i);
BIT.update(num[i],BIT.dp[i]);
}
int Max = BIT.query(1).len;
for(int i=Max;i;i--){
for(int j=vec[i].size()-1;~j;j--){
int pos = vec[i][j];
if(BIT.dp[pos].cnt<k) k -= BIT.dp[pos].cnt;
else{
vis[num[pos]] = 1;
while(now<pos) BIT.dp[++now].cnt = 0;
break;
}
}
}
cout<<n-Max<<'\n';
for(int i=1;i<=n;i++) if(!vis[i]) cout<<i<<'\n';
return 0;
}
着重解释一下枚举子集。
int Max = BIT.query(1).len; // 表示求出最长上升子序列长度
for(int i=Max;i;i--){
for(int j=vec[i].size()-1;~j;j--){ // 后加进去的位于数列前面,字典序更小
int pos = vec[i][j];
if(BIT.dp[pos].cnt<k) k -= BIT.dp[pos].cnt; // 如果该点为起点个数不足K个,则直接跳过
else{
vis[num[pos]] = 1; // 不足K个,所以这一位确定
while(now<pos) BIT.dp[++now].cnt = 0; // 删除前面的记录,不能再用了
break;
}
}
}
\(END\)