【NOIP2008】双栈排序
Description
Tom最近在研究一个有趣的排序问题。如图所示,通过2个栈S1和s2,Tom希望借助以下4种操作实现将输入序列升序排序。
1、操作a
如果输入序列不为空,将第一个元素压入栈S1
2、操作b
如果栈S1不为空,将S1栈顶元素弹出至输出序列
3、操作c
如果输入序列不为空,将第一个元素压入栈s2
4、操作d
如果栈S2不为空,将S2栈顶元素弹出至输出序列
如果一个1~n的排列P可以通过一系列操作使得输出序列为l,2,…,(n-1),n,Tom就称P是一个“可双栈排序排列”。例如 (1,3,2,4)就是一个“可双栈排序排列”,而(2,3,4,1)不是。下图描述了一个将(1,3,2,4)排序的操作序 列:
当然,这样的操作序列有可能有多个,对于上例(1,3,2,4),是另外一个可行的操作序列。Tom希望知道其中字典序最小的操作序列是什么。
solution
好难的题啊.
分析一个栈的情况:只能是一个或多个递减连续序列组成的,如 \(3,2,1,6,5,4,9,8,7\),如果 \(3,2,1\) 之间出现了 \(3,5,1\) 的情况,就会存在问题,严格的讲,就是满足 \(i<j,a[i]<a[j]\),且 \(j\) 后面存在比 \(i\) 小的数 这样的二元组 \((i,j)\),但是双栈排序就可以很好的解决,我们需要把每一个元素分配给两个栈,然后模拟单栈排序的过程即可,至于分配的条件:如果我们把 \((i,j)\) 连边,那么有连边的不能二元组在同一个集合中,这就是二分图的定义,我们二分图染色一下即可,字典序的问题只要我们把前面的元素尽量分给第一个栈就可以满足最小了
#include <algorithm>
#include <iostream>
#include <cstdlib>
#include <cstring>
#include <cstdio>
#include <cmath>
#define RG register
#define il inline
#define iter iterator
#define Max(a,b) ((a)>(b)?(a):(b))
#define Min(a,b) ((a)<(b)?(a):(b))
using namespace std;
typedef long long ll;
const int N=1005;
int n,a[N],dp[N],num=0,head[N],to[N*N*2],nxt[N*N*2],col[N];
void link(int x,int y){nxt[++num]=head[x];to[num]=y;head[x]=num;}
inline void dfs(int x){
for(int i=head[x];i;i=nxt[i]){
int u=to[i];
if(col[u]){
if(col[u]+col[x]!=3){puts("0");exit(0);}
continue;
}
col[u]=3-col[x];
dfs(u);
}
}
int now=1,st[N],skt[N],top=0,sktop=0;
inline void P(char c){putchar(c);putchar(' ');}
inline void solve(int x){
if(col[x]==1)st[++top]=a[x],P('a');
else skt[++sktop]=a[x],P('c');
while((top && st[top]==now) || (sktop && skt[sktop]==now)){
if(st[top]==now)top--,P('b');
else if(skt[sktop]==now)sktop--,P('d');
now++;
}
}
void work()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)scanf("%d",&a[i]);
dp[n]=a[n];dp[n+1]=N;
for(int i=n-1;i>=1;i--)dp[i]=Min(dp[i+1],a[i]);
for(int i=1;i<=n;i++){
for(int j=i+1;j<=n;j++){
if(a[i]<a[j] && a[i]>dp[j])
link(i,j),link(j,i);
}
}
for(int i=1;i<=n;i++)
if(!col[i]){
col[i]=1;
dfs(i);
}
for(int i=1;i<=n;i++)solve(i);
}
int main()
{
work();
return 0;
}