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\)

posted @ 2025-05-31 11:39  OrangeRED  阅读(15)  评论(0)    收藏  举报