【USACO 2018 December Contest, Platinum Problem 2】Sort It Out [JZOJ100129] 排序【DP】【线段树】

29 篇文章 0 订阅

Description

以下是翻译魔改版题面:

FJ有N条胖头鱼(分别用1…N编号)排成一行。
FJ喜欢他的胖头鱼以升序排列,不幸的是现在他们的顺序被打乱了。
在过去FJ曾经使用一些诸如“冒泡排序”的开创性的算法来使他的胖头鱼排好序,但今天他想偷个懒。
取而代之,他会每次对着一条胖头鱼叫道“按顺序排好”。
当一条胖头鱼被叫到的时候,他会确保自己在队伍中的顺序是正确的(从他的角度看来)。
只要有一条紧接在他右边的胖头鱼的编号比他小,他们就交换位置。
然后,只要有一头紧接在他左边的胖头鱼的编号比她大,他们就交换位置。
这样这条胖头鱼就完成了“按顺序排好”,在这条胖头鱼看来左边的胖头鱼编号比他小,右边的胖头鱼编号比他大。
FJ想要选出这些胖头鱼的一个子集,然后遍历这个子集,依次对着每一条胖头鱼发号施令(按编号递增的顺序),重复这样直到所有N条胖头鱼排好顺序。
例如,如果他选出了编号为{2,4,5}的胖头鱼的子集,那么他会喊叫胖头鱼2,然后是胖头鱼4,然后是胖头鱼5。
如果N条胖头鱼此时仍未排好顺序他会再次对着这几条胖头鱼喊叫,如果有必要的话继续重复。
由于FJ不确定哪些胖头鱼比较专心,他想要使得这个子集最小。此外,他认为K是个幸运数字。请帮他求出满足重复喊叫可以使得所有胖头鱼排好顺序的最小子集之中字典序第K小的子集。
我们称{1,…,N}的一个子集S在字典序下小于子集T,当S的所有元素组成的序列(按升序排列)在字典序下小于T的所有元素组成的序列(按升序排列)。例如,{1,3,6} 在字典序下小于{1,4,5}。

N<=10^5 ,K<=10^18

Solution

既然我们要选择一个子集,这里面的数可以动,那么也就意味着删除这个子集中的数后剩下的部分相对顺序不能改变,也就是要求要是递增的。

要求删去的自己最小,就是让剩下的部分最大,就是最长上升子序列。
求字典序第K小,那么剩下的部分字典序就是第K大。
问题转化为求字典序第K大的最长上升子序列。

我们需要求出两个值 f [ i ] , g [ i ] f[i],g[i] f[i],g[i],分别表示以位置i开头的最长上升子序列的长度以及数量。
f [ i ] f[i] f[i]是经典做法, g [ i ] g[i] g[i]可以用树状数组来做(树状数组维护一个f,g的二元组),也可以无脑上线段树

接下来只需要一位一位确定就行了,把所有位置按f[i]分组,对于同一组的排序以后扫过去确定就好。

时间复杂度 O ( n log ⁡ n ) O(n\log n) O(nlogn)

Code

#include <bits/stdc++.h>
#define fo(i,a,b) for(int i=a;i<=b;++i)
#define fod(i,a,b) for(int i=a;i>=b;--i)
#define N 100005
#define LL long long
using namespace std;
int n,f[N],a[N],d[N],rt[N],n1,t[40*N][2],df[N];
LL m,g[N],sm[40*N];
vector<int> dc[N];
bool bz[N];
bool cmp(int x,int y)
{
	return x>y;
}
bool cmp2(int x,int y)
{
	return a[x]<a[y];
}
void nwp(int &x) {if(!x) x=++n1;}
void ins(int k,int l,int r,int x,LL v)
{
	if(l==r&&l==x) {sm[k]+=v;if(sm[k]>m+1) sm[k]=m+1;return;}
	int mid=(l+r)>>1;
	if(x<=mid) nwp(t[k][0]),ins(t[k][0],l,mid,x,v);
	else nwp(t[k][1]),ins(t[k][1],mid+1,r,x,v);
	sm[k]=sm[t[k][0]]+sm[t[k][1]];
	if(sm[k]>m+1) sm[k]=m+1;
}
LL query(int k,int l,int r,int x,int y)
{
	if(!k||!sm[k]||x>y||x>r||y<l) return 0;
	if(x<=l&&r<=y) return sm[k];
	int mid=(l+r)>>1;
	return query(t[k][0],l,mid,x,y)+query(t[k][1],mid+1,r,x,y);
}
int main()
{
	cin>>n>>m;
	fo(i,1,n) scanf("%d",&a[i]);
	int l=0;
	d[0]=n+1;
	fod(i,n,1)
	{
		int w=upper_bound(d,d+l+1,a[i],cmp)-d;
		if(w>l) rt[++l]=++n1;
		d[w]=max(d[w],a[i]);
		f[i]=w,g[i]=(w==1)?1:query(rt[w-1],1,n,a[i]+1,n);
		dc[w].push_back(i);
		ins(rt[w],1,n,a[i],g[i]);
	}
	int ml=0;
	fod(i,l,1)
	{
		int r=dc[i].size();
		d[0]=0;
		fo(j,0,r-1) if(a[dc[i][j]]>a[ml]&&dc[i][j]>ml) d[++d[0]]=dc[i][j];
 		sort(d+1,d+d[0]+1,cmp2);
		while(g[d[d[0]]]<m) m-=g[d[d[0]]],--d[0];
		bz[ml=d[d[0]]]=1; 
	}
	memset(d,0,sizeof(d));
	fo(i,1,n) if(!bz[i]) d[++d[0]]=a[i];
	sort(d+1,d+n-l+1);
	printf("%d\n",n-l);
	fo(i,1,n-l) printf("%d\n",d[i]);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值