解题思路:本题的难点在于挖掘题目给定问题的性质,首先来看一下我们到底能从这个问题中发现什么有利于我们做题的性质吧。
性质1:如果一上来就给定p=1的情况,让我们把从q下标以后的数组升序排列的话,那这些操作是直接可以忽略掉的,因为我们原本给定的序列就是升序的,所以第一个有效的操纵一定是p=0,将某个前缀变为倒序的操作。
性质2:如果有连续的几个前缀或者是后缀操作,那我们只需要记录其中操作范围最大的一个即可,如下图所示:
三个红色线段代表了3次前缀操作,对于这种情况,我们只需要保留最长的操作也就是op3操作即可,因为只要op3操作是包含了前2个操作的。
性质3:如果当前性质的操作,举个例子当前的前缀操作比它隔着一个后缀操作的上一个前缀操作的范围大,那么可以直接将这个前缀操作的前2个操作给删掉。
像这种情况2次前缀操作(红色线段)中间夹杂了一个后缀操作(紫色线段),实际上最后结果的存储也是一个前缀操作,一个后缀操作交替的这是由于性质2所决定的,回到当前情况,我们发现前缀操作和后缀操作有一段是重合的(粉色区域),现在来看一下将前2个操作删除后是否和只保留最后一个操作是等价的,第一个前缀操作将红色部分倒叙,第二个操作将紫色区域按顺序排序,因为前缀操作是倒叙操作,所以排在最后的一定是较小的一部分,又因为后缀操作是顺序排序,所以后缀操作之后粉色部分里面的数还是原来的那些数,只是顺序变了而已,至于紫色的剩余部分保持不变就好了,最后再将红色部分倒叙,通过上面的分析我们可以得出,只要有第3个前缀操作在,前2个操作是可以直接去掉的。
性质4:通过性质3的分析,我们可以发现,在最终的答案中保存下来的操作前缀操作和后缀操作的范围都是逐渐减小的,也就是说重合部分越来越小,我们每次需要进行翻转的部分(前后缀操作重合的部分)也越来越小,也就是说随着重合部分的缩小,后缀后面的部分和前缀前面的部分已经是确定好了的,我们只需要对确定的部分按要求的顺序进行填充就好了。
代码:
#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
using namespace std;
const int N =1E5+10;
int n,m;
int ans[N];
pair<int,int>stk[N];
int main()
{
cin>>n>>m;
int top=0;
while(m--)
{
int p,q;
cin>>p>>q;
if(p==0)
{
while(top&&stk[top].first==0)
q=max(q,stk[top--].second);//性质2
while(top>=2&&stk[top-1].second<=q)//性质3
top-=2;
stk[++top]={0,q};
}
else if(top)//性质1
{
while(top&&stk[top].first==1)
q=min(q,stk[top--].second);
while(top>=2&&stk[top-1].second>=q)
top-=2;
stk[++top]={1,q};
}
}
int k=n,r=n,l=1;
for(int i=1;i<=top;i++)
{
if(stk[i].first==0)
while(r>stk[i].second&&l<=r) ans[r--]=k--;//性质4
else
while(l<stk[i].second&&l<=r) ans[l++]=k--;
if(l>r)
break;
}
//如果说还剩下一段没有进行填写,要再次进行判断要填写的部分的顺序
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;
}
花了一天终于弄明白了,呜呜呜QAQ