来自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;
}
案例运行结果: