题目(转自洛谷)
Tom最近在研究一个有趣的排序问题。如图所示,通过2个栈S1和S2 ,Tom希望借助以下4种操作实现将输入序列升序排序
操作a
如果输入序列不为空,将第一个元素压入栈S1
操作b
如果栈S1空,将S1栈顶元素弹出至输出序列
操作c
如果输入序列不为空,将第一个元素压入栈S 2
操作d
如果栈S2不为空,将S2栈顶元素弹出至输出序列
如果一个1−n的排列P可以通过一系列操作使得输出序列为1,2,…,(n-1),n1,2,…,(n−1),n,Tom就称PP是一个“可双栈排序排列”。例如(1,3,2,4)(1,3,2,4)就是一个“可双栈排序序列”,而(2,3,4,1)(2,3,4,1)不是。下图描述了一个将(1,3,2,4)(1,3,2,4)排序的操作序列:<a,c,c,b,a,d,d,b><a,c,c,b,a,d,d,b>
当然,这样的操作序列有可能有几个,对于上例(1,3,2,4)(1,3,2,4),<a,c,c,b,a,d,d,b><a,c,c,b,a,d,d,b>是另外一个可行的操作序列。Tom希望知道其中字典序最小的操作序列是什么。
输入格式
第一行是一个整数n。
第二行有n个用空格隔开的正整数,构成一个1−n的排列。
输出格式
共一行,如果输入的排列不是“可双栈排序排列”,输出数字00;否则输出字典序最小的操作序列,每两个操作之间用空格隔开,行尾没有空格。
输入输出样例
输入 #1
4
1 3 2 4
输出 #1
a b a a b b a b
输入 #2
4
2 3 4 1
输出 #2
0
输入 #3
3
2 3 1
输出 #3
a c a b b d
说明/提示
30%的数据满足: n≤10
50%的数据满足:n≤50
100%的数据满足:n≤1000
观察数据规模,发现这道题对时间复杂度的要求并不高,n2的算法可以轻松通过,甚至连logn n2也是可以的,再观察题干,发现这道题如果跳过模拟直接得出答案很困难,凭一己之力很难找到规律,所以最开始还是要从模拟当中找到答案。但是直接模拟也很难。让人有一种无从下手的感觉,那我们还是从特殊到一般试试。看见“双”,先想“单”,假设我们只有一个栈,怎么排序?在排序中,栈的作用到底是什么?仔细想一想,不难发现其实就是辅助交换逆序对,并且它的能力有限,对于2 3 1这样的数据是无能为力的。
那么“2 3 1”,这种数据有什么性质?它不单调!,对于没有单调性质的数据。我们会发现都不可以用单栈排序。
那么扩展到双栈呢?,这种情况下,我们就可以将1压进1号栈,2压进2号栈,3压进1号栈,再分别弹出3,1,2,就完成了排序。
那当其中有一个栈失去了单调性质,那么这个排序也就gg了。所以说,我们要判断可不可以用双栈排序,本质上就是要判断数列中存不存在a4<a1<a2<a3这一类的数据。当然我们可以暴力判O(4^n) , 也可以优化一下先预处理出后缀最小值,然后O(n^3 )判断,当然都会TLE。
那怎么办?两个栈交替存储输出,不就是一个二分图嘛?那利用二分图的思想,就能轻松在n2的时间复杂度内解决这道题了。
附上代码~
关于二分图的一些技巧就在注释里面了
#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;
const int Inf=0x3f3f3f3f;
int n,cnt=0,not_bip=0;//cnt记录操作数,目测cnt最后会等于2*n因为每个数都要进栈和出栈
int len1=0,len2=0;//记录两个栈的大小
int Wait_print=1;//我们当前该输出啥数
int dt[1010][1010];//存图
int Sm[1010];//后缀最小值
int sl[1010];//原数列
int Q[1000010];//Bfs队列
int col[1010];//每个数的颜色
int vis[1010];//Bfs visit数组
int stack1[1010];//1栈
int stack2[1010];//2栈
char Oper[2020];//操作,这个数组可以不开,大家可以在过程中直接输出,而且会更快
void Suffix_min()//预处理后缀最小值
{
Sm[n]=sl[n];
for(int i=n-1;i>=1;i--)
{
Sm[i]=min(Sm[i+1],sl[i]);
}
}
void build_edge()//建边
{
memset(dt,0x3f,sizeof(dt));
for(int i=1;i<=n;i++)
{
for(int j=i+1;j<=n;j++)
{
if(Sm[j]<sl[i]&&sl[i]<sl[j])
{
//满足这种条件下,单栈GG了,那么把i,j放到不同的栈里
dt[i][j]=1;//先在ij之间添一条边 ,
dt[j][i]=1;//等会染色就可以把他们标记上不同的颜色
}
}
}
}
void Bipartitegraph_dyeing(int poi)//这个函数名翻译成中文叫:二分图染色
{
//我用Bfs的方法用0/1进行染色。
memset(Q,0,sizeof(Q));
int head=1,tail=0;
Q[++tail]=poi;
col[poi]=0;
vis[poi]=1;
while(head<=tail)
{
int st=Q[head];
for(int i=1;i<=n;i++)
{
if(dt[st][i]!=Inf&&col[i]==col[st])
{//这个条件满足,就说明该图不是二分图,
not_bip=1;
return ;
}//也就是说存在了a4<a1<a2<a3这种操作 。
if(dt[st][i]!=Inf&&col[i]==-1&&!vis[i])
{
col[i]=1-col[st];
vis[i]=1;
Q[++tail]=i;
}
}
head++;
}
}//这样,我们就把所有数分成了col=1和col=0两类,0压1栈,1压2栈
void Simulate()//我们来模拟一下排序过程(保证字典序最小)
{
sl[0]=Inf;//避免在len--时减到负数
Oper[++cnt]='a';//先把第一个数压进 1栈
stack1[++len1]=1;
for(int i=2;i<=n;i++)
{
if(col[i]==0)
{
//我们要维护两个栈内的数都是单调不升的
while(sl[i]>sl[stack1[len1]])
{
Oper[++cnt]='b';
len1--;
Wait_print++;
//根据我们上面的过程,这里弹出去的一定符合顺序
//不会把较大的数先弹出去
}
Oper[++cnt]='a';
stack1[++len1]=i;
}
if(col[i]==1)
{
//这里要注意了,因为我们要字典序最小
//所以在往2栈填数时,先考虑一下1栈可不可以弹数
while(sl[stack1[len1]]==Wait_print)
{
Oper[++cnt]='b';
len1--;
Wait_print++;
}
while(sl[i]>sl[stack2[len2]])
{
Oper[++cnt]='d';
len2--;
}
Oper[++cnt]='c';
stack2[++len2]=i;
}
}
while(len1>0&&len2>0)
{
if(sl[stack1[len1]]<=sl[stack2[len2]])
{
Oper[++cnt]='b';
len1--;
}
else
{
Oper[++cnt]='d';
len2--;
}
}
for(int i=len1;i>=1;i--)
{
Oper[++cnt]='b';
}
for(int i=len2;i>=1;i--)
{
Oper[++cnt]='d';
}
}
int main()
{
scanf("%d",&n);//读入n
for(int i=1;i<=n;i++)
{
//读入数列
scanf("%d",&sl[i]);
}
Suffix_min();
build_edge();
memset(col,-1,sizeof(col));
for(int i=1;i<=n;i++)
{
if(col[i]==-1)
{
Bipartitegraph_dyeing(i);
}
}
if(not_bip)
{
printf("0\n");
return 0;
}
Simulate();
for(int i=1;i<=cnt-1;i++)
{
printf("%c ",Oper[i]);
}
printf("%c",Oper[cnt]);
return 0;
}
本文探讨了一种使用两个栈进行升序排序的问题,详细分析了如何通过特定操作序列实现排序。对于给定的1-n排列,文章讨论了如何判断是否能通过双栈排序,以及如何找到字典序最小的操作序列。作者提到了从特殊到一般的思考方法,从单栈到双栈的转换,并指出问题可以转化为二分图问题,从而在n^2的时间复杂度内解决。文章附带了解决此问题的代码实现。

1037

被折叠的 条评论
为什么被折叠?



