- 题目
Description
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希望知道其中字典序最小的操作序列是什么。
Input
输入有多组Case,每个Case第一行是一个整数n(n<=1000)。
第二行有n个用空格隔开的正整数,构成一个1~n的排列。
Output
每组Case输出一行,如果输入的排列不是“可双栈排序排列”,输出数字0;否则输出字典序最小的操作序列,每两个操作之间用空格隔开,行尾没有空格。
41 3 2 442 3 4 1
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.特殊情况与注意点:
- 代码
#include <iostream>
#include <cmath>
#include <stack>
#include <string>
using namespace std;
// global
bool edges[1005][1005]; // if there is edges between pos i and pos j num
int color[1005]; // color of each num, 0 initial, 1 color A, 2 color B
bool can_output; // if can be sort
// put color on num using dfs
void 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;
}
#include <iostream>
#include <cmath>
#include <stack>
#include <string>
using namespace std;
// global
bool edges[1005][1005]; // if there is edges between pos i and pos j num
int color[1005]; // color of each num, 0 initial, 1 color A, 2 color B
bool can_output; // if can be sort
// put color on num using dfs
void 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://tieba.baidu.com/p/503212184,http://www.cnblogs.com/Blacko/p/3373313.html
(推荐参考代码)http://blog.csdn.net/kqzxcmh/article/details/9566813