Noip2008双栈排序

【问题描述】

  用两个栈使一个1...n的排列变得有序。一共有四个操作:

  A.stack1.push()   读入一个放入栈一

  B.stack1.pop()   弹出栈一放入输出序列

  C.stack2.push()  读入一个放入栈二

  D.stack2.pop()  弹出栈二放入输出序列

  给你一个初始的排列,求一个字典序最小的操作序列使得变得有序,若没有满足条件的操作序列,输出‘0‘。

  

  Sample.in                                Sample.out

  4     1 3 2 4                               a b a a b b a b

  4     2 3 4 1                               0

  3     2 3 1                                  a c a b b d

【分析】

  嗯嗯嗯...这题好有意思啊...然后开始埋头苦干,发现好神奇啊!

  1.栈原来真的可以排序诶!

  2.双栈原来也可以排序诶!

  3.原来第二个样例真的不能排序诶!

  ....于是沉默了....于是开始纠结....什么样的数据不能用双栈排序呢?

  ........

  思考了很久很久.....似乎想不出来什么东西....

  不过我在构造中发现....每当一个栈要挂的时候,往往可以祸水东引,将即将堵住的元素放到另一个栈中去,每次两个栈都堵住的时候...恩恩,双栈排序就挂了!

  于是开始想另一个问题:什么样的数据不能用单栈排序....

  随便造了一个:2 3 1 恩,就挂了。

  因为2应该在3前出去,但是因为1在后面,所以2出不去,就被3堵住了....

  所以只要有 i<j<k 满足 a[k]<a[i]<a[j] 就可以卡死一个栈了....

  【题外】【严谨证明】【copy from codevs】【不想看的记住这个结论就好了】

      考虑对于任意两个数a[i]和a[j]来说,它们不能压入同一个栈中的充要条件是什么(注意没有必要使它们同时存在于同一个栈中,只是压入了同一个栈).

      实际上,这个条件p是:存在一个k,使得i<j<k且a[k]<a[i]<a[j].    [猜对了有木有!]

      首先证明充分性,即如果满足条件p,那么这两个数一定不能压入同一个栈.这个结论很显然,使用反证法可证.

    假设这两个数压入了同一个栈,那么在压入a[k]的时候栈内情况如下:

    …a[i]…a[j]…
    因为a[k]比a[i]和a[j]都小,所以很显然,当a[k]没有被弹出的时候,另外两个数也都不能被弹出(否则输出序列中的数字顺序就不是1,2,3,…,n了).
    而之后,无论其它的数字在什么时候被弹出,a[j]总是会在a[i]之前弹出.而a[j]>a[i],这显然是不正确的. 

      接下来证明必要性.也就是:如果两个数不可以压入同一个栈,那么它们一定满足条件p.    这里我们来证明它的逆否命题,也就是"如果不满足条件p,那么这两个数一定可以压入同一个栈."
    不满足条件p有两种情况: 1. 对于任意i<j<k且a[i]<a[j] , a[k]>a[i]; 2. 对于任意i<j , a[i]>a[j].
      第一种情况下,很显然,在a[k]被压入栈的时候,a[i]已经被弹出栈.那么,a[k]不会对a[j]产生任何影响(这里可能有点乱,因为看起来,当a[j]<a[k]的时候,是会有影响的,但实际上,这还需要另一个数r,满足j<k<r且a[r]<a[j]<a[k],也就是证明充分性的时候所说的情况…而事实上我们现在并不考虑这个r,所以说a[k]对a[j]没有影响).
      第二种情况下,我们可以发现这其实就是一个降序序列,所以所有数字都可以压入同一个栈.
    这样,原命题的逆否命题得证,所以原命题得证. 

    此时,条件p为q1[i]和q1[j]不能压入同一个栈的充要条件也得证. 

  

  所以如果遇到这样的 i,j,它们一定不能丢到一个栈里面去,于是给它们并查集连个虚点?或者连条什么边[二分图染色]。

  唔...下面就好办啦....[我打的二分图染色,就讲这个哈]

  二分图染色 [只有两种颜色1,0] 就是在每一个连通块上,对一个点染上黑色[belong[x]=1],然后对所有连的边染上和它不同的颜色[belong[v]=1^belong[x]],当然还得给每个元素下一个是否已经找到颜色的标记sure[],以免多次染色和漏掉矛盾。[矛盾的情况很好办咯...就是发现两个已经确定的,而且练了边的点染成了同一个颜色!....然后就枪毙了...]

  因为要求操作序列的字典序最小,所以我们希望前面的元素能尽可能放进栈一中,假设栈一中的都是颜色为黑的,那么只需要按照顺序对每一个不曾染过色的点染成黑色,然后拓展它所在的连通块即可。

  最后,我们都知道了所有点在哪个栈中....就可以开始开心的模拟了!

  记录一个cot表示现在希望弹出的元素是几,发现可以弹出cot的时候一直弹弹弹[ 弹走鱼尾纹 ] 就可以了,具体还不懂就看代码咯.....[有贴心小注释]

  

  

 1 #include<cstdio>
 2 #include<cstring>
 3
 4 using namespace std;
 5
 6 inline int in(){    //读入优化
 7     int x=0;char ch=getchar();
 8     while(ch>‘9‘ || ch<‘0‘) ch=getchar();
 9     while(ch>=‘0‘ && ch<=‘9‘) x=x*10+ch-‘0‘,ch=getchar();
10     return x;
11 }
12
13 const int maxn=1010;
14
15 struct Node{    //携带本人代码气息的邻接表
16     int data,next;
17 }node[maxn*maxn];
18
19 #define now node[point].data
20 #define then node[point].next
21
22 int n,cnt,t1,t2,cot=1;
23 int a[maxn],head[maxn],belong[maxn];
24 int f[maxn],s1[maxn],s2[maxn];
25 bool sure[maxn],over;
26
27 inline int Min(int a,int b){
28     if(a>b) return b;return a;
29 }
30
31 inline void add(int u,int v){    //邻接表加边
32     node[cnt].data=v;node[cnt].next=head[u];head[u]=cnt++;
33     node[cnt].data=u;node[cnt].next=head[v];head[v]=cnt++;
34 }
35
36 void dfs(int x){
37     sure[x]=true;
38     for(int point=head[x];point!=-1;point=then)
39         if(sure[now]){    //如果相邻的点已经被染过色
40             if(belong[now]^belong[x]);    //说明相邻两个点的颜色不同
41             else{over=true;return;}
42         }
43         else    //如果相邻的点未曾染色
44             belong[now]=1-belong[x],dfs(now); //染色,并扩展所在连通分块
45 }
46
47 inline void Pop(){    //弹出栈的函数,因为弹出一个栈,cot会 ++,所以可能会带来连锁弹栈
48     while(s1[t1]==cot || s2[t2]==cot){
49         while(s1[t1]==cot && t1)
50             putchar(‘b‘),putchar(‘ ‘),cot++,t1--;
51         while(s2[t2]==cot && t2)
52             putchar(‘d‘),putchar(‘ ‘),cot++,t2--;
53     }
54 }
55
56 int main(){
57     n=in();
58     for(int i=1;i<=n;i++) a[i]=in(),head[i]=-1;
59
60     //对于任意两个数a[i]和a[j]来说,它们不能压入同一个栈中的充要条件是 : 存在一个k,使得i<j<k且a[k]<a[i]<a[j]
61     f[n+1]=0x7f7f7f7f;
62     for(int i=n;i;i--)
63         f[i]=Min(f[i+1],a[i]);    //预处理出当前位置往后最小的 a[k]
64
65     for(int i=1;i<n;i++)
66     for(int j=i+1;j<n;j++)
67         if(a[i]<a[j] && a[i]>f[j+1]) add(i,j);
68
69     for(int i=1;i<=n;i++)
70         if(!sure[i]){
71             dfs(i); //a[i]所在连通分块中,在序列中的第一个元素染成白色,也就是放入栈 1中[因为 belong[]的初值为 0]
72             if(over){printf("0");return 0;}
73         }
74
75     for(int i=1;i<=n;i++){
76         if(belong[i])    //模拟压栈
77             putchar(‘c‘),putchar(‘ ‘),s2[++t2]=a[i];
78         else
79             putchar(‘a‘),putchar(‘ ‘),s1[++t1]=a[i];
80         if(a[i]==cot) Pop();
81     }
82     return 0;
83 }

时间: 2024-10-09 19:12:01

Noip2008双栈排序的相关文章

NOIP2008 双栈排序 染色+模拟

挺不错的一道题,首先可以知道若存在形如 k<i<j 但 a[k]<a[i]<a[j]这样的,那么i,j一定不能(从始至终不能)进入同一个栈 例如 2 3 1,若2 3进入同一个栈,那么1再进栈然后马上出栈,这时候,2没有办法在3之前出来. 所以对于这样的i,j我们连一条边,然后dfs染色,若染色中发现相邻点颜色相同,则无解,否则我们按照1,2,1,2的顺序染色. 确定了每一个数属于哪个栈后,用2个stack模拟一下就好了. #include <iostream> #in

NOIP2008 双栈排序

题目描述 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)就是一个“可

二分图 and code1170 双栈排序

6.6二分图 二分图是这样一个图: 有两顶点集且图中每条边的的两个顶点分别位于两个顶点集中,每个顶点集中没有边直接相连接. 无向图G为二分图的充分必要条件是,G至少有两个顶点,且其所有回路的长度均为偶数. 判断二分图的常见方法是染色法: 开始对任意一未染色的顶点染色,之后判断其相邻的顶点中,若未染色则将其染上和相邻顶点不同的颜色, 若已经染色且颜色和相邻顶点的颜色相同则说明不是二分图,若颜色不同则继续判断,bfs和dfs都可以. 易知:任何无回路的的图均是二分图. 代码: bool Color(

双栈排序(codevs 1170)题解

[问题描述] 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)就是一个

AC日记——双栈排序 洛谷 P1155

双栈排序 思路: 二分图染+模拟: 代码: #include <bits/stdc++.h> using namespace std; #define maxn 1005 #define maxm 2000005 int n,head[maxn],E[maxm],V[maxm],cnt,col[maxn]; int minn[maxn],ai[maxn],sta1[maxn],sta2[maxn],top1,top2; bool if_[maxn][maxn]; inline void in(

BZOJ 2080: [Poi2010]Railway 双栈排序

2080: [Poi2010]Railway Time Limit: 10 Sec  Memory Limit: 259 MBSubmit: 140  Solved: 35[Submit][Status][Discuss] Description 一个铁路包含两个侧线1和2,右边由A进入,左边由B出去(看下面的图片) 有n个车厢在通道A上,编号为1到n,它们被安排按照要求的顺序(a1,a2,a3,a4....an)进入侧线,进去还要出来,它们要按照编号顺序(1,2,3,4,5....n)从通道B

洛谷——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)就是一个“可

P1155 双栈排序

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是一个"可双栈排序排列".例

[CTCI] 双栈排序

 双栈排序 题目描述 请编写一个程序,按升序对栈进行排序(即最大元素位于栈顶),要求最多只能使用一个额外的栈存放临时数据,但不得将元素复制到别的数据结构中. 给定一个int[] numbers(C++中为vector<int>),其中第一个元素为栈顶,请返回排序后的栈.请注意这是一个栈,意味着排序过程中你只能访问到第一个元素. 测试样例: [1,2,3,4,5] 返回:[5,4,3,2,1] 1 class TwoStacks { 2 public: 3 vector<int> t