noip 2008 双栈排序

 洛谷——P1155 双栈排序

题目描述

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希望知道其中字典序最小的操作序列是什么。

输入输出格式

输入格式:

 

输入文件twostack.in的第一行是一个整数n。

第二行有n个用空格隔开的正整数,构成一个1~n的排列。

 

输出格式:

 

输出文件twostack.out共一行,如果输入的排列不是“可双栈排序排列”,输出数字0;否则输出字典序最小的操作序列,每两个操作之间用空格隔开,行尾没有空格。

 

输入输出样例

输入样例#1:
【输入样例1】
4
1 3 2 4
【输入样例2】
4
2 3 4 1
【输入样例3】
3
2 3 1

输出样例#1:
【输出样例1】
a b a a b b a b
【输出样例2】
0
【输出样例3】
a c a b b d

说明

30%的数据满足: n<=10

50%的数据满足: n<=50

100%的数据满足: n<=1000

 

思路:

我们先来考虑一下单栈排序。

对于单栈排序,我们可以这样想,后进入栈中的数一定要比先进入栈中的数小(因为这个地方栈满足先入栈者后出栈)。

能单栈排序的情况:

   不存在(i<j<k)且 v[k]<v[i]<v[j]的情况即可进行单栈排序。

对于以上情况:当j要进栈时,i必须出栈;但k又必须在i前出栈,但k又必须在i后入栈,当然不可以。

充分性:

当i<j<k时,除了上述情况(v[k]<v[i]<v[j])还有

a。当v[i]>v[j]时 i,j可以同时在栈中,无论v[k]的植如何,都能进行排序。

b。当v[i]<v[j]<v[k]是也可以排序

c.当v[i]<v[k]<v[j]时也能排序

所以,综上所述,除了i<j<k &&v[k]<v[i]<v[j]的情况都能排序。

单栈排序我们考虑完了,我们再从单栈问题转到双栈问题。

我们先来考虑这样一个问题:对于一个序列s,什么时候s[i],s[j]可以放在一个序列里??(我们在这里所说的能放在一个序列里,非单纯的能否同时放在一个序列里,而是自始至终不能放在一个序列里,即如果有解,那么s[i],s[j]一定不再用一个序列里)

这里给出一个结论:s[i],s[j]两个元素不能进入同一个栈<——>(等价于)存在k满足i<j<k使得s[k]<s[i]<s[j]

要证明吗?不要的话就可以直接略过下面这一段。

我们看到i<j<k就是说明i一定比j先入栈,j一定比k先入栈。而给定的关系s[k]<s[i]<s[j]时要满足当j入栈时,i必须出栈,当i入栈时,k有必须出栈,而我们又知道i先于k入栈(而栈又满足:先入栈者后出栈,后入栈者先出栈),所以我们只需要让k进栈后接着就出来,这样显然就一定满足整个条件

 我们把每个元素按照输入的顺序进行编号,看作每一个图中的每个顶点,这时,我们对于所有的(i,j)判断是否满足上述结论,即s[i]s[j]是否能进入同一个栈中。如果满足上述结论,我们在i,j之间连一条边。

这样我们就把我们所拥有的条件处理成一个图,接下来我们要做的就是判断这个图是否是二分图就好了,在判断是否是二分图时我们用到了二分图染色。

我们对图进行染色,由于我们只有两个栈,我们得到的图必须是二分图才能满足条件。由于要求字典序最小,当我们让元素进栈时让该元素尽量多得进1栈。我们按照编号递增的顺序从每个未染色的顶点开始染色,相邻的顶点上染不同的颜色,如果发生冲突,则是无解的。否者我们可以得到每个顶点的颜色,即进入的栈。这样我们就把我们所要求的问题转化成一个十分裸的二分图染色问题了。

二分图?!

定义:

设G=(V,E)是一个无向图。如顶点集V可分割为两个互不相交的子集,并且图中每条边依附的两个顶点都分属两个不同的子集。则称图G为二分图。也就是说在二分图中,顶点可以分为两个集合X和Y,每一条边的两个顶点都分别位于X和Y集合中。

 二分图染色:

思想和步骤:我们任意取出一个顶点进行染色,该点和相邻的点有3种情况:1.未染色,这样我们就继续染色它的相邻点(染为另一种颜色)

2.已染色 在这种情况下我们判断其相邻点染得色与该点是否相同。若不同,跳过该点。

3。已染色 且其相邻点的颜色与该点的颜色相同 返回FALSE (即改图不是二分图)

拓展:

下面我们补充几个概念:

1,如果一个强连通分量内的某些顶点在一个集圈内(即某些强通分量内含有奇圈),那么强连通分量中的其它顶点也在某个奇圈内。  

       第一个条件的证明:我们假设有一个奇圈,因为是点双,没有割点,必然有紧挨着的圈,假设这个是偶数圈,则,这个偶数圈必然能和原来的奇圈组成新的奇圈(因为:新的圈=(奇数圈-k)+(偶数圈-k)=奇数+偶数-偶数=奇数,k是共同边上的点数

2.如果一个双联通分量含有奇圈,着他必定不是一个二分图。反过来也成立,这是一个充要条件。

 

代码:

 

#include<queue>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
#define N 1001
#define maxn 9999999
using namespace std;
int n,tot,sum1,sum2,head[N],a[N];
int col[N],que[N],t,h,s1[N],s2[N],f[N];
char q[N],p[N],flag;
struct Edge
{
    int from,to,next;    
}edge[N];
void add(int from,int to)
{
    ++tot;
    edge[tot].to=to;
    edge[tot].next=head[from];
    head[from]=tot;
}
void paint(int s)
{
    queue<int> q;
    q.push(s);
    col[s]=0;//我们把放在第一个栈中的颜色染成0  
    if(flag) return ;
    while(!q.empty())
    {
        int x=q.front();
        for(int j=head[x];j;j=edge[j].next)
        {
            if(col[edge[j].to]!=-1)//说明已被染过色 
            {
                if(col[edge[j].to]==col[x])//并且染的色与该点相同,这样就说明其不是二分图 
                  flag=true;
            }
            else
            {
                col[edge[j].to]=col[x]^1;//用另一种颜色染其相邻的点 
                q.push(edge[j].to);
            }
        }
        q.pop();
    }
}
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)    scanf("%d",&a[i]);
    f[n+1]=maxn;
    for(int i=n;i>=1;i--)
      f[i]=min(f[i+1],a[i]);//预处理从每一个点出发到序列末端的最小值,也可以用RMQ 
    for(int i=1;i<=n;i++)//我们挨个判断不能让在同一个栈中的点,对其进行连边,枚举序列内起点 
     for(int j=i+1;j<=n;j++)//由于我们在这两步中枚举的是A数组的下标,我们又要找不能放在一个栈中的情况
     //在前面我们有说到:对于i<j<k&&a[K]<a[i]<a[j]的情况是一定不能放在同一个栈中的 
      if(a[i]>f[j+1]&&a[i]<a[j])//在这里我们就是找上述的情况 
       add(i,j),add(j,i);//不能放在一个栈中,连边 
    memset(col,-1,sizeof(col));//初始化col数组,方便我们判断与一个点相连的点是否被染过色。 
   for(int i=1;i<=n;i++)//在这里我们不一定把她连成整的一棵树,所以我们要循环枚举所有相连的节点,把它全部染色 
    if(col[i]==-1)
     paint(i);//染色 
    if(flag) {printf("0\n");return 0;}//不能用两个栈进行排序
    int now=1;//我们要把给定的序列a排成有序的序列,并且所给的数构可构成一个1~n的排列
    for(int i=1;i<=n;i++)
    {
        if(col[i]==0)
        {
            if(a[i]==now) printf("a b "),now++;
            else  
            {
                while(s1[sum1]==now) sum1--,printf("b "),now++;
                s1[++sum1]=a[i],printf("a ");
            }
        }
        else
        {
            if(a[i]==now) printf("c d "),now++;
            else 
            {
                while(s1[sum1]==now) sum1--,printf("b "),now++;//注意:你在这里出栈的时候,对应的序列也已经排完了一个 
                s2[++sum2]=a[i],printf("c ");
            }
        }
    }
    for(int i=now;i<=n;i++)
    {
        while(i==s1[sum1]&&sum1) sum1--,printf("b ");
        while(i==s2[sum2]&&sum2) sum2--,printf("d ");
    }
    return  0;
}

 

转载于:https://www.cnblogs.com/z360/p/6954002.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值