双端队列
达达现在碰到了一个棘手的问题,有N个整数需要排序。
达达手头能用的工具就是若干个双端队列。
她从1到N需要依次处理这N个数,对于每个数,达达能做以下两件事:
1.新建一个双端队列,并将当前数作为这个队列中的唯一的数;
2.将当前数放入已有的队列的头之前或者尾之后。
对所有的数处理完成之后,达达将这些队列按一定的顺序连接起来后就可以得到一个非降的序列。
请你求出最少需要多少个双端序列。
输入格式
第一行输入整数N,代表整数的个数。
接下来N行,每行包括一个整数Di,代表所需处理的整数。
输出格式
输出一个整数,代表最少需要的双端队列数。
数据范围
1≤N≤200000
输入样例:
6
3
6
0
9
6
3
输出样例:
2
我们面对这个问题时,可能首先想到的是利用队列的思想解决或模拟队列的方法解决,但由于队列的个数未知,最优的方法又太复杂太难考虑;这里我们利用贪心的思想,所谓贪心就是我们先假定已知答案,然后去推性质,最终我们利用性质的唯一性去求取答案。
我们先考虑一个双端队列的情况:(这里我们举例是为了方便大家直观得到的性质,实际情况可能会有所不同,但这里的性质是通用的)按3、2、4、5、1、的顺序入队双端队列的话则有:
下面的标号说明的是入栈的顺序
我们可以看到当一个双端队列升序排列好之后,其插入顺序(或其位置下标)是有明显的下降在上升的过程的,这也很好理解中间的元素是最开始插入的,之后插入的小数就往前排越小的越靠前同理越大的越靠后;这样我们就可以现将元素的大小排列好然后通过分隔位置的升降段落来得到最终的双端队列的最小的个数。
#include<iostream>
#include<algorithm>
using namespace std;
const int N=200010;
typedef pair<int ,int > PII;
PII a[N];
int n;
int main(){
ios::sync_with_stdio(false);
cin>>n;
for(int i=0;i<n;i++)
{
cin>>a[i].first;
a[i].second=i;
}
sort(a,a+n);//对元数据进行第一元素升序排序(相同时第二元素升序排序)
int res=1;//最少的双端队列是1
for(int i=0,last=n+1,dir=-1;i<n;)
{ //last为上一位的位置,我们假定一开始是位置降序,那么将初始位置设置为n+1
int j=i;//j为本轮移动到的位置
while(j<n&&a[i].first==a[j].first)j++;//当遇到元素值相同时,将位置移动到这一段相同之外
int minx=a[i].second,maxx=a[j-1].second;//将两个位置定位到该段元素的两头
if(dir==-1)//当上一轮判断是位置降序时
{
if(last>maxx)last=minx;//由于第一元素相同时第二元素也是升序排列的
//所以当上一位置大于该段最后位置时,也一定大于该段最初位置
//即仍然符合降序性质
else//当该段不符合降序性质时,即我们要进入升序序列
{
dir=1;//标志设置为升序
last=maxx;//由于进入升序序列我们将位置指向第二元素最大
}
}
else//当上一轮判断是升序序列时
{
if(last<minx)last=maxx;//当上一轮位置小于该段最小位置时,也一定小于最大位置,将位置移动到最大位置
else//当经历了议论降序升序时,说明进入了一个新的双端队列
{
res++;
last=minx;//进入降序序列位置移动到最小位置
dir=-1;//标志设置为降序
}
}
i=j;//将位置更新
}
cout<<res<<endl;
return 0;
}