sicily双栈排序

来源:互联网 发布:手机宣传视频制作软件 编辑:程序博客网 时间:2024/05/16 07:35

  • 题目

Description

Tom最近在研究一个有趣的排序问题。如图所示,通过2个栈S1S2Tom希望借助以下4种操作实现将输入序列升序排序。

操作a:如果输入序列不为空,将第一个元素压入栈S1

操作b:如果栈S1不为空,将S1栈顶元素弹出至输出序列

操作c:如果输入序列不为空,将第一个元素压入栈S2

操作d:如果栈S2不为空,将S2栈顶元素弹出至输出序列

如果一个1~n的排列P可以通过一系列操作使得输出序列为12,…,(n-1)nTom就称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希望知道其中字典序最小的操作序列是什么。

Input

输入有多组Case,每个Case第一行是一个整数n(n<=1000)

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

Output

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

Sample Input
41 3 2 442 3 4 1
Sample Output
a b a a b b a b


  • 解析

1.先考虑单栈情况:

定理:考虑对于任意两个数q[i]和q[j],它们不能压入同一个栈中的充要条件: 存在一个k,使得i<j<k且q[k]<q[i]<q[j]。
证明:
充分性:即如果满足上述条件,那么q[i]和q[j]一定不能压入同一个栈。

反证法:假设这两个数压入了同一个栈,那么压入q[k],因为q[k]比q[i]和q[j]都小,所以很显然,当q[k]没有被弹出的时候,另两个数也都不能被弹出(否则输出序列的数字顺序就不是1,2,3,…,n了)。而之后,无论其它的数字在什么时候被弹出,q[j]总是会在q[i]之前弹出,而q[j]>q[i],这显然是不正确的.
必要性:如果两个数不可以压入同一个栈,那么它们一定满足上述条件。
证明逆否命题:也就是"如果不满足上述条件,那么这两个数一定可以压入同一个栈。”不满足上述条件有两种情况:情况1:对于任意i<j<k且q[i]<q[j],q[k]>q[i];(即对任意三个数,最小的总是在最前面)情况2:对于任意i<j,q[i]>q[j]。    第一种情况:在q[k]被压入栈的时候,q[i]已经被弹出栈。那么,q[k]不会对q[j]产生任何影响(这里可能有点乱,因为看起来,q[j]<q[k]的时候是会有影响的,但实际上,这还需要另一个数r,满足j<k<r且 q[r]<q[j]<q[k],也就是证明充分性的时候所说的情况。而事实上我们现在并不考虑这个r,所以说q[k]对q[j]没有影响)。
第二种情况:可以发现这其实就是一个降序序列,所以所有数字都可以压入同一个栈。这样,原命题的逆否命题得证,所以原命题得证。


2. 双栈情况:

2.1 判断是否有解和任意两个数能否压入同一个栈

(1) 对任意两个数q[i]和q[j],若存在一个k,使得i<j<k且q[k]<q[i]<q[j],则这两个数分别入s1栈和s2栈


(2) 将s1栈和s2栈中的数字分成两个顶点子集,同一顶点子集内不会出现任何连边,即不能压入同一个栈的所有数字被分到了两个栈中。任意两个不在同一栈的数字间连边。此时我们只考虑检查是否有解,所以只要花O(n)时间检查这个图是不是二分图,就可以得知是否有解了。

*(二分图是一种特殊类型的图:图中的顶点集被划分成X与Y两个子集,图中每条边的两个端点一定是一个属于X而另一个属于Y。二分图的匹配是求边的一个子集,该子集中的任意两条边都没有公共的端点。)


2.2 解题步骤:

(1) 检查数列中的数字是否满足进入同一个栈的条件,如果不满足则将边连上,构造二分图。


(2) 用dfs染色,
把二分图染成1和2两种颜色,使得染色为1的结点被压入s1栈,染色为2结点被压入s2栈。具体:每次选取一个未染色的编号最小的结点,将它染色为1,并从它开始dfs染色,直到所有结点都被染色为止。这样,我们就得到了每个结点应该压入哪个栈中。注意dfs结束之后未被染色的数字是可以被放入同一个栈的,所以优先染色为1。如果发现某一个数字两次要染不同的颜色,则为不能输出。


(3) 模拟出栈操作。


3.特殊情况与注意点:

3.1 为了判断是否满足同一个栈的条件,需要对数组进行预先处理,得到一个记录每一位数字之后最小数字的数列。

3.2 模拟输出的时候,考虑这样一种情况:此时将栈1排空了,可以压入数字,也可以继续输出栈2的数字。如果模拟的时候直接顺序排列a, b, c, d四种操作,则b结束后,因为预先染色不能压入c,则进行d操作了。这是不对的,因为需要先压入栈1,再排出栈2,顺序应该是a d而不是d a。从这个角度来说,找到的很多代码其实是错误的。需要增加一个循环元素,如果排空栈1之后可以压入则回到循环顶部先压入s1在去输出栈2的数字。

  • 代码
#include <iostream>#include <cmath>#include <stack>#include <string>using namespace std;// globalbool edges[1005][1005]; // if there is edges between pos i and pos j numint color[1005]; // color of each num, 0 initial, 1 color A, 2 color Bbool can_output; // if can be sort// put color on num using dfsvoid dfs(int keyPos, int color_, int n) {  color[keyPos] = color_;  for (int i = 0; i < n; i++) {    if (edges[keyPos][i]) {      if (color[i] == color_) {        can_output = false;        break;      } else if (!color[i]) {        dfs(i, 3 - color_, n);      }    }    if (edges[i][keyPos]) {      if (color[i] == color_) {        can_output = false;        break;      } else if (!color[i]) {        dfs(i, 3 - color_, n);      }    }  }}int main () {  int n;  int num[1005];  int postMin[1005]; // smallest num behind each pos  while (cin >> n) {    // initialize    for (int i = 0; i < 1005; i++) {      color[i] = 0;      for (int j = 0; j < 1005; j++) {        edges[i][j] = false;      }    }    can_output = true;    // input    for (int i = 0; i < n; i++) {      cin >> num[i];    }    // compute postMin    for (int i = n - 1; i >= 0; i--) {      if (i == n - 1) {        postMin[i] = num[i];      } else {        postMin[i] = min(num[i], postMin[i + 1]);      }    }    // make edges    //int count = 0;    for (int i = 0; i < n - 1; i++) {      for (int j = i + 1; j < n; j++) {        if (num[i] < num[j] && postMin[j] < num[i]) {          // cannot be put into same stack;          edges[i][j] = edges[j][i] = true;          //count++;        }      }    }    //cout << "Edges: " << count << endl;    // color the num    for (int i = 0; i < n; i++) {      if (!color[i]) {        dfs(i, 1, n);      }    }    // output    // cout << "Start output" << endl;    stack<int> s1, s2;    string order;    if (can_output) {      // simulate output      // cout << "Start simulation" << endl;      int output_num = 1; // the num to be output      int pos = 0; // the pos of the numbers      // no edge between two num means they can be put into one stack      while (output_num <= n) {        if (color[pos] == 1) {          order += "a";          s1.push(num[pos++]);          // cout << "Push: " << num[pos - 1] << " a" << endl;        }        while (!s1.empty() && s1.top() == output_num) {          // cout << "Pop: " << s1.top() << " b" << endl;          s1.pop();          order += "b";          output_num++;        }        if (color[pos] == 1 && (s1.empty() || s1.top() > num[pos])) {          continue;        }        while (!s2.empty() && s2.top() == output_num) {          // cout << "Pop: " << s2.top() << " d" << endl;          s2.pop();          order += "d";          output_num++;        }        if (color[pos] == 2) {          order += "c";          s2.push(num[pos++]);          // cout << "Push: " << num[pos - 1] << " c" << endl;        }      }      for (int i = 0; i < order.length(); i++) {        if (i) {          cout << " ";        }        cout << order[i];      }      cout << endl;    } else {      cout << "0" << endl;    }    while (!s1.empty()) {      s1.pop();    }    while (!s2.empty()) {      s2.pop();    }    order.clear();  }  return 0;}

  • 解题过程与总结

1. 前前后后花了一天多的时间来写。先是自己写,之后花了很多时间调试都是WA。没有想到数学原理,直接考虑如果栈顶数字大于现在的数字就可以压入,否则不能压入;优先压入栈1;如果两个栈都不能压入也不能输出则为错误。这是一种贪心算法思路,每一步求最优解。但这样没有考虑到有时候能压入栈1的情况下也必须压入栈2的情况。因此,其实贪心算法缺乏对于数据整体的先处理,或者是进行每一步时都对新数据进行存储和完善以便之后选择。

2. 之后参考其他博客的思路,自己重新写,花了很多时间而且代码质量差。开始还是WA,后来发现模拟过程有误,又花了很多时间进行对特殊情况的修改。之后结果能够正确,但是因为自己加入边并存储的方式和dfs算法写的非常烂,结果超时。之后还是按照其他博客的参考代码进行修改。

3. 对于输出的模拟情况,需要考虑的内容很多,花了大量的时间进行修改,参考其他博客的题解,但是没有找到一个非常简洁而又正确的写法。总体而言,在图算法、离散数学相关的算法方面非常缺乏练习也没有付出努力去练习过。

  • 参考资料

(解题思路)http://zhidao.baidu.com/link?url=olvz7jCjua-wBM8OBknsWkKOPYBVm5xOyY6evemWdDxMileb3U_UqNZebPuDPD_8kz5rmlU74BpmKrwnkrhKn_

(解题思路与代码)http://tieba.baidu.com/p/503212184,http://www.cnblogs.com/Blacko/p/3373313.html

(推荐参考代码)http://blog.csdn.net/kqzxcmh/article/details/9566813





0 0