双栈排序[NOIP2008 T4/CH1813]

题面描述
Tom最近在研究一个有趣的排序问题。通过2个栈S1和S2,Tom希望借助以下4种操作实现将输入序列升序排序。

操作a
如果输入序列不为空,将第一个元素压入栈S1
操作b
如果栈S1不为空,将S1栈顶元素弹出至输出序列
操作c
如果输入序列不为空,将第一个元素压入栈S2
操作d
如果栈S2不为空,将S2栈顶元素弹出至输出序列

如果一个1~n的排列P可以通过一系列操作使得输出序列为1, 2,…,(n-1), n,Tom就称P是一个"可双栈排序排列"。例如(1, 3, 2, 4)就是一个"可双栈排序序列",而(2, 3, 4, 1)不是。下图描述了一个将(1, 3, 2, 4)排序的操作序列:<a, c, c, b, a, d, d, b>

当然,这样的操作序列有可能有几个,对于上例(1, 3, 2, 4),<a, c, c, b, a, d, d, b>是另外一个可行的操作序列。Tom希望知道其中字典序最小的操作序列是什么。
思路
我们通过观察可以发现,
由于要最小字典序,我们要尽可能将数压入 S 1 S_1 S1
那什么时候将数压入 S 2 S_2 S2呢?
我们设有 i , j , k i,j,k i,j,k这三个变量,满足 i < j < k i<j<k ijk,且 a [ k ] &lt; a [ i ] &lt; a [ j ] a[k]&lt;a[i]&lt;a[j] a[k]<a[i]<a[j]
此时这种情况 i , j i,j i,j一定不能同时被压入同一个栈

反证法
i , j i,j i,j压入同一个栈,
k k k要先出栈,所以 i i i就进栈后要等 k k k出栈才能出栈,
而无论 k k k在哪里,都无法让 i i i先于 j j j出栈。
故该命题不成立。
得证。
此时,我们易观察到,这就是 i i i j j j建边后,就是一个二分图。
若违背二分图规则,则不满足题意。
代码

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<cstdlib>
#include<stack>
using namespace std;
const int N=1e6+10;
const int M=1e3+10;
const int inf=1e9+10;
struct edge{int x,y,next;}a[N];int len,last[M];
void ins(int x,int y){a[++len].x=x,a[len].y=y;a[len].next=last[x];last[x]=len;}
int clr[M];bool bk;
int s[M],b[M],n;
void get(int x,int c)
{
	clr[x]=c;
	for(int k=last[x];k;k=a[k].next)
	{
		int y=a[k].y;
		if(clr[y]==c){bk=false;return ;}
		if(!clr[y])get(y,3-c);
	}
}
void solve()
{
	bk=true;
	memset(clr,0,sizeof(clr));memset(b,0,sizeof(b));
	len=0;memset(last,0,sizeof(last));scanf("%d",&n);
	for(int i=1;i<=n;i++)scanf("%d",&s[i]),b[i]=s[i];
	b[n+1]=inf;
	for(int i=n;i>=1;i--)if(b[i+1]<b[i])b[i]=b[i+1];
	for(int i=1;i<=n;i++)
	{
		for(int j=i;j<=n;j++)
		{
			if(b[j+1]<s[i]&&s[j]>s[i])ins(i,j),ins(j,i);
		}
	}
	for(int i=1;i<=n;i++)if(!clr[i])get(i,1);
	if(!bk){puts("0");return ;}
	stack<int>st1,st2;int cnt=1,num=1;
	for(int i=1;i<=n;i++)
	{
		if(clr[i]==1)
		{
			st1.push(s[i]);
			putchar('a');cnt++;
			if(cnt<=2*n)putchar(' ');
		}
		else
		{
			st2.push(s[i]);
			putchar('c');cnt++;
			if(cnt<=2*n)putchar(' ');
		}
		while((!st1.empty()&&st1.top()==num)||(!st2.empty()&&st2.top()==num))
		{
			if(!st1.empty()&&st1.top()==num)
			{
				num++;st1.pop();
				putchar('b');cnt++;
				if(cnt<=2*n)putchar(' ');
			}
			else if(!st2.empty()&&st2.top()==num)
			{
				num++;st2.pop();
				putchar('d');cnt++;
				if(cnt<=2*n)putchar(' ');
			}
		}
	}
	puts("");
}
int main()
{
	//freopen("input","r",stdin);
	int t;scanf("%d",&t);
	while(t--)solve();
	return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值