奇怪的排序题解

来自bzoj.org/p/P06331(传统题)

题面:


给你一个数字N,再给你一个N的全排列。

希望你对这个全排列按权值大小进行升序排列,但给你可用的工具却非常奇葩。

给一个长条桌子,上面有若干个空位置,在这些空位置上,你可以新建立一个柱子出来

希望你对这个全排列从左扫到右,对于扫到的第i个数字ai

你可以进行以下两种选择之一

1:将ai放到某个已建好的柱子的顶部

2:在桌子上从左到右,找到一个空位置,并建立一个新柱子,将ai放在柱子上。

当然在放ai前,你也可以在一个非空的柱子上,取出其最上面的元素,将其丢到结果数组中

并希望结果数组中所放的数字,其值是升序排列的,且序列长度越长越好

基本问题:


这道题和'空当接龙游戏(bzoj.org/p/Z2149)'很类似,同样是要用到单调栈。

由于这道题要用到多个栈,所以傻傻的(bushi)开那么多栈是不行的,不然会炸的很爽...

其实,我们可以通过以下几个数组来实现n个栈:

top[x] = 第x个栈的头元素。

under[x] = 数字x下面的元素。

last[x] = 第x个栈的尾元素。(本题特殊需要)

sum = 栈的数量。

也就是说,用这几个数组,能模拟出一个“栈群”,从而初步解决问题。


核心思路:


无论如何开栈,最后的读数永远是从左往右,从顶到底的弹出。

所以,我们的单调栈必须是严格上升的,为了保证最优解,每一个数要放在栈顶元素大于这个数且栈顶元素尽量小的栈里

int x;
cin >> x;
if(x > top[sum])
{
	sum++;
	top[sum] = x;
	tot[sum] = 1;
}
else
{
	int t = upper_bound(top + 1,top + 1 + n,x) - top;
	under[x] = top[t];
	top[t] = x;
	tot[t]++;
}

但是,题目不止这么简单,题目中说的取出栈顶元素非常的重要


特殊案例:

例如4 5 2 3 1这个序列,它运行起来是怎样的呢?

4 单独开栈。

5 单独开栈。

2 加入1号栈,1号栈变为{4,2}。

3加入2号栈,2号栈变为{5,3}。

1加入1号栈,1号栈变为{1,4,2}。

这时,1号栈出栈后,出栈序列变为1,2,4,这时候,3和5就出不了栈了

这时,提前出栈就派上了大用场。

如果在3进栈前,把2出栈,再把3加入1号栈,这样虽然会导致1无法出栈,但出栈序列可以延长至2,3,4,5。

很明显,我们要把upper_bould中的top换成last,当上文中的top[t]小于x时,不断弹出栈顶元素,并记录下最大值,若后续某个数小于这个最大值,则停止进栈并输出结果。

if(last[sum] < x) 
{
	sum++;
	last[sum] = top[sum] = x;
	continue;
}
else 
{
	int p = upper_bound(last + 1,last + 1 + sum,x) - last;
	int t = top[p];
	while(t && t < x)
	{
		maxn = max(maxn,t);
		t = under[t];
	}
	if(!t) last[p] = top[p] = x;
	else
	{
		under[x] = t;
		top[p] = x;
	}
}

 最后,如果没有出现特殊情况,答案就是n。


参考代码:

#include <bits/stdc++.h>
using namespace std; 
int n,sum;
int top[100005],last[100005],under[100005];
int main()
{
	cin >> n;
	int maxn = 0;
	for(int i = 1;i <= n;i ++) 
	{
		int x;
		cin >> x;
		if(x < maxn) 
		{
			cout << i - 1;
			return 0;
		}
		if(last[sum] < x) 
		{
			sum++;
			last[sum] = top[sum] = x;
			continue;
		}
		else 
		{
			int p = upper_bound(last + 1,last + 1 + sum,x) - last;
			int t = top[p];
			while(t && t < x)
			{
				maxn = max(maxn,t);
				t = under[t];
			}
			if(!t) last[p] = top[p] = x;
			else
			{
				under[x] = t;
				top[p] = x;
			}
		}
	}
	cout << n;
}

案例运行结果:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值