【第一届文翁杯现场竞技赛T3】文翁点名

【题目】

传送门

题目描述:

文翁班上有 n n n 个学生,每次上体育课他们会随机排列为一排,每个人有一个编号 a i a_i ai 1 1 1 ~ n n n 的排列),现在文翁希望让所有人按编号升序排成一排,可是他不希望采取普通的排序方法来完成。

他开创了一个有趣的排列方法,每一次他会点名一个学生,被点名的学生会确保他的顺序正确,当右边挨着他的人编号比他小,就和右边交换,然后当左边挨着他的人编号比他大他也会交换位置。这样,被点名的学生就完成了按他认为的按顺序排好(在他看来左边的学生编号比他小,右边的学生编号比他大)。

文翁需要选出一个学生编号子集,遍历这个子集,依次叫子集中每个人出列,重复下去直到所有人都排好序。如他选出子集 { 1 , 3 , 6 } \{1,3,6\} {1,3,6},他就会先叫 1 1 1,再叫 3 3 3,再叫 6 6 6。如果这 n n n 个人还没有排好序,他会再次叫 1 , 3 , 6 1,3,6 1,3,6

很明显这样的子集很多,现在文翁希望得到的子集最小且能完成排序,这样他觉得还不够,他希望得到满足条件的字典序第 k k k 小子集。(保证存在至少 k k k 个符合要求的子集。)

输入格式:

输入的第一行包含两个整数 n n n k k k。含义如题目描述。

第二行包含 n n n 个空格分隔的整数,表示从左到右学生的编号。

输出格式:

第一行输出最少需要选择多少学生数量。

接下来若干行输出在所有可能子集中的字典序 k k k 小的集合编号(从小到大排序后输出)。

样例数据:

【样例 1 1 1

输入
4 1
4 2 1 3

输出
2
1
4

【样例 2 2 2

输入
6 1
6 2 4 3 1 5

输出
3
1
3
6

提示:

【样例 1 1 1 解释】

开始的时候序列为 4213 4213 4213 。在文翁叫编号为 1 1 1 的学生之后,序列变为 1423 1423 1423 。在文翁叫编号为 4 4 4 的学生之后,序列变为 1234 1234 1234 。在这个时候,序列已经完成了排序。

【数据规模与约定】

20 % 20\% 20% 的数据, n ≤ 6 n≤6 n6,并且 k = 1 k=1 k=1

另外有 30 % 30\% 30% 的数据, k = 1 k=1 k=1

100 % 100\% 100% 的数据, 1 ≤ n ≤ 1 0 5 , 1 ≤ k ≤ 1 0 18 1\le n\le10^5,1≤k≤10^{18} 1n105,1k1018


【分析】

这道题有一个前置知识啦:最长上升子序列的数量。

先转换一下题目的意思。

对于题目中一个可行的集合 B,由于我们只对 B 集合里的元素换位置, ∁ A B \complement _AB AB 中元素的相对位置始终没有变,也就是说 ∁ A B \complement _AB AB 需要构成一个上升序列。由于 B 集合要最小,那么 ∁ A B \complement _AB AB 就要最大,就是最长上升子序列

又因为我们要找出字典序第 k k k 小的 B 集合,那就找出字典序第 k k k 大的最长上升子序列即可。

那么,对于每个点我们都保存两个值, l e n ( i ) len(i) len(i) 表示以 i i i 开头的 LIS 长度, c n t ( i ) cnt(i) cnt(i) 表示以 i i i 开头的 LIS 数量。

然后按照 l e n ( i ) len(i) len(i) 为下标将每个位置 i i i 存进 v e c t o r vector vector 中,即 v e c x vec_x vecx 中保存的是所有长度为 x x xLIS 开头的位置。

我们使用试填法的思想求解。每次,我们选择一个下标最大的位置使得这个位置合法。具体来说,按照 LIS 长度从大到小枚举每一个 v e c t o r vector vector,对于每个 v e c t o r vector vector 从大到小不断的枚举下标直到可用的 LIS 总数大于等于 k k k,我们就可以选取当前枚举到的数来充当 LIS 的这一位。当然,每次填的数必须递增,可以通过跳过比这一位小的数来做到这点。试填完毕后按顺序输出不在 LIS 中数的下标即可。

时间复杂度为 O ( n log ⁡ 2 n ) O(n\log_2n) O(nlog2n)


【代码】

#include<cstdio>
#include<vector>
#include<cstring>
#include<algorithm>
#define N 100005
#define ll long long
#define lowbit(x) ((x)&(-x))
using namespace std;
int n,a[N];
const ll limit=1e18+5;
struct lis
{
	int len;ll cnt;
	void operator+=(const lis &b)
	{
		if(len>b.len)  return;
		if(len<b.len)  *this=b;
		else  cnt=min(cnt+b.cnt,limit);
	}
}f[N],bit[N];
vector<int>vec[N];
bool vis[N];
void add(int i,const lis &x)
{
	for(;i;i-=lowbit(i))  bit[i]+=x;
}
lis query(int i)
{
	lis ans=(lis){0,1};
	for(;i<=n;i+=lowbit(i))  ans+=bit[i];
	return ans;
}
int main()
{
	long long k;
	int i,j,Max,now=0;
	scanf("%d%lld",&n,&k);
	for(i=1;i<=n;++i)
	  scanf("%d",&a[i]);
	for(i=n;i>=1;--i)
	{
		f[i]=query(a[i]+1);
		f[i].len++,add(a[i],f[i]);
		vec[f[i].len].push_back(i);
	}
	Max=query(1).len;
	for(i=Max;i>=1;--i)
	{
		for(j=vec[i].size()-1;~j;--j)
		{
			int pos=vec[i][j];
			if(f[pos].cnt<k)  k-=f[pos].cnt;
			else
			{
				vis[a[pos]]=true;
				while(now<pos)  f[++now].cnt=0;
				break;
			}
		}
	}
	printf("%d\n",n-Max);
	for(i=1;i<=n;++i)if(!vis[i])printf("%d\n",i);
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值