给定序列 (a1,a2,···,an)=(1,2,···,n),即 ai=i。
小蓝将对这个序列进行 m 次操作,每次可能是将 a1,a2,···,aqi 降序排列,或将 aqi,aqi+1,···,an 升序排列。
请求出操作完成后的序列。
输入格式
输入的第一行包含两个整数 n,m,分别表示序列的长度和操作次数。
接下来 m 行描述对序列的操作,其中第 i 行包含两个整数 pi,qi 表示操作类型和参数。当 pi=0时,表示将 a1,a2,⋅⋅⋅,aqi 降序排列;当 pi=1 时,表示将 aqi,aqi+1,···,an 升序排列。
输出格式
输出一行,包含 nn 个整数,相邻的整数之间使用一个空格分隔,表示操作完成后的序列。
数据范围
对于 30%的评测用例,n,m≤1000;
对于 60%的评测用例,n,m≤5000;
对于所有评测用例,1≤n,m≤1e5,0≤pi≤1,1≤qi≤n。
输入样例:
3 3
0 3
1 2
0 2
输出样例:
3 1 2
样例解释
原数列为 (1,2,3)。
第 1 步后为 (3,2,1)。
第 2 步后为 (3,1,2)。
第 3 步后为 (3,1,2)。与第 2 步操作后相同,因为前两个数已经是降序了。
本题直接暴力用sort会通过60%,附:反向sort(a+l,a+r,greater<int>())
思路解析:
将所有操作划分为前缀操作(p=0)和后缀操作(p=1)
前缀操作:将[0,r]区间内所有数降序排序
后缀操作:将a[l,n]区间内所有数升序排列
首先,第一次进行有意义操作的一定是前缀
因为本来序列就是升序的(1,2,3..),第一次前缀操作前的后缀操作相当于没有操作,反正都是升序
如下是第一次前缀操作
前半段红色部分逆序,后半段虚线部分不用动,和上面一样
假如下面是接着前缀操作的一次后缀操作:
将与上面红色部分相交的部分逆序即可,后面的蓝色部分本来就是升序的不用动
前面虚线部分也不用动
再接着上面一次后缀操作的前缀操作:
前半部分不用动,又是只需要将中间相交的部分逆序即可
再来一次后缀操作:
相交部分逆序,前后不变
总结:每次只需要翻转红色区间即可
用栈保存{p,q},p是表示是前缀还是后缀操作,q表示的是操作端点位置,top表示栈顶下标
注意点1:
前面提到了,第一次一定是前缀操作,第一次前缀操作前的后缀操作毫无意义,所以只有top!=0时
才能放后缀操作在栈中:
注意点2:
上面画的图都是1个前缀,1个后缀接着的模型,假如是连续的前缀或后缀怎么办呢?
假如是连续的前缀操作,
我们在每次进行存储操作的时候,将栈内的当前栈顶所有的连续前缀操作提取出来,直到遇到后缀操作,提取就停止,每次提取和当前待存储的前缀操作的端点相比较,取max,最后,在栈顶存一个右端点最大的前缀操作
假如是连续的后缀操作,可以用相同的做法,取出所有的连续后缀操作,比较出min即可
到此为止,我们在栈内存储的就应该是1个前缀接上一个后缀的模型了
注意点3:
当我们发现如果下一个前缀操作右端点大于上一个存储在栈内的前缀操作,如图所示:
也就是在进行一次前缀操作和后缀操作后,下一次的前缀操作在上一次的前缀操作的节点后,这个时候我们可以把前两次操作给删去,直接进行这一次的前缀操作,因为上一次的后缀操作和前缀操作都包含在了这一次的前缀操作内,前两次操作等于是没用的,所以我们只需要保留当前操作即可
相当于上面的删除x,y操作
综上:
所有有效的前缀和后缀操作都是严格变小的,不断压缩相交的红色区间,每次操作的部分就是相交的红色区间
一直进行如下操作:更新右端点,翻转相交区间,更新左端点,翻转相交区间,更新......
我们可以知道,每次只会操作一个红色的区间,区间还在不断缩小,所以区间以外的部分永远不会变,那么这区间以外的部分是啥呢?
右半边空白的部分就是(n,n-1,n-2...)倒着排,左半边填的时候由于已经进行了降序,所以也是接着倒着排(n-3,n-4....),就相当于倒着将n到1填入即可,最终红色区间消失
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
#define x first
#define y second
typedef pair<int, int> PII;
const int N = 1e5+10;
PII stk[N];//x->前缀还是后缀,y->操作的下标
int n,m;
int ans[N];//存结果
int main()
{
cin>>n>>m;
int top=0;
while (m -- )
{
int p,q;
cin>>p>>q;
if(p==0)
{
while(top && stk[top].x==0) q=max(q,stk[top--].y);
while(top>=2 && stk[top-1].y<=q) top-=2;
stk[++top]={0,q};
}
else if(top)//一定得是先有前缀操作,后缀操作才有意义
{
while(top && stk[top].x==1) q=min(q,stk[top--].y);
while(top>=2 && stk[top-1].y>=q) top-=2;
stk[++top]={1,q};
}
}
int k=n,l=1,r=n;
for(int i=1;i<=top;i++)
{
if(stk[i].x==0)//前缀操作,左半边降序,右半边正常顺序排即可,由于从右向左,相当于倒着填入n,n-1,...
{
while(r>stk[i].y && l<=r) ans[r--]=k--;
}
else//后缀操作,由于前缀操作使得左半边降序,所以也是从左向右倒着填入n,n-1,...即可
{
while(l<stk[i].y && l<=r) ans[l++]=k--;
}
if(l>r) break;
}
//所有操作全部做完,但还没填满,l<r
if(top%2)//如果操作数是奇数,那么栈内最后一个是前缀操作,是从右往左填的,下一次就应该从左往右填
{
while(l<=r) ans[l++]=k--;
}
else//如果操作数是偶数,那么栈内最后一个是后缀操作,是从左往右填的,下一次就应该从右往左填
{
while(l<=r) ans[r--]=k--;
}
for(int i=1;i<=n;i++) cout<<ans[i]<<" ";
return 0;
}