双栈排序
noip 2008
题目链接:http://soj.me/1768
刚开始看到这题还以为这是一种排序方式,仔细一看这是一道acm神题啊,好难好难,看了好多别人的代码终于懂了,于是……写个博客装下逼。
首先说的是,这题不可贪心,因为贪心法的话总会遇到一种情况——当两个栈都可以放时,不知道放哪个栈,又根据贪心的原则,我们很高兴的把它优先放入了第一个栈,
测了一下样例,哇塞对了耶,然后提交,之后就WA了(给跪。。。)
这里大概的说一下原因,因为我们优先放入第一个栈,但是不对啊,放入了第一个栈可能导致后面一些东西没地方放,即影响了之后的操作,
这里给一个大神提供的样例:7 5 7 2 4 1 6 3 (一般的贪心都会错了吧,感觉。。。)。
正确的做法是需要预处理的:
首先我们想想单栈排序,我们知道,如果有n个栈,就一定能排序成功,那一个栈呢,什么情况下一个栈无法排序,答案是:
如果出现 i < j < k 但是 a[k] < a[i] < a[j] 的情况,则无法单栈排序,至少要两个栈
在这里我们可以引入离散数学的二分图思想,如果这个序列能用一个栈排序,则是一分图;如果能用两个栈,则是二分图!
不知道说清楚了没……总而言之,就是要建图,将每个数当作一个结点,按某种条件建边,即不能放入同一个栈,之后判断该图是否为二分图,如果是则必然能进行双栈排序!
总的来说算法分成了三步:建图 -> 染色 -> 模拟
建图
一般的做法大概就是用一个三重循环判断各种 i ,j ,k 的组合,但是在这题里面 o( n^3 ) 的时间复杂度会超时,所以我们用一个minx数组进行优化
minx[i] 表示第 i 个及 i 之后的所有数字的最小值,我们需要一个for循环来计算minx[0] ~ minx[n-1] ,这是一个 o( n ) 复杂度的操作
然后可以进行建图,原来的建图条件可以简化成如果 i < j , minx[j+1] < a[i] < a[j] ,则 i 与 j 之间存在边,这样就是二重循环可以解决的了
染色
普通的深搜染色,分别染成 1 和 2 ,顺便可以判断一下是否是二分图
模拟
如果刚刚染色失败了,那就直接输出 0
如果颜色是 1 ,则放进第一个栈,否则进第二个;同时要不断判断栈顶元素是否是该输出的元素
po上代码瞧一瞧:
#include <iostream> #include <cstdlib> #include <cstring> #include <cstdio> #include <string> #include <algorithm> #include <stack> using namespace std; int n,flag; int a[1010],minx[1010],col[1010]; bool map[1010][1010]; char st[10010]; void color(int index,int c) //对某个点进行染色 { col[index]=c; for (int i=0;i<n;i++) if(map[index][i]) { if(col[i]==c){ flag=0; //标记染色失败 return; } if(!col[i]) //如果没有染过色 color(i,3-c); } } int main() { while(scanf("%d",&n)!=EOF) { flag=1; //标志 memset(col,0,sizeof(col)); memset(map,0,sizeof(map)); memset(st,0,sizeof(st)); for (int i=0;i<n;i++) scanf("%d",&a[i]); //下面过程为判断任意两个点是否相连,时间复杂度由普通的 o(n^3) 降到了 o(n^2) minx[n-1]=a[n-1]; //找到后 i 个元素的最小值 for (int i=n-2;i>=0;i--) minx[i]=min(minx[i+1],a[i]); for (int i=0;i<n-2;i++) //连边 for (int j=i+1;j<n-1;j++) if(a[i]<a[j]&&minx[j+1]<a[i]) map[i][j]=map[j][i]=1; for (int i=0;i<n;i++) //染色 if(!col[i]) color(i,1); if(!flag){ printf("0\n"); continue; } int num=1,cnt=0; stack<int> s1,s2; for (int i=0;i<n;i++) //模拟排序过程 { if(col[i]==1) { st[cnt++]='a'; s1.push(a[i]); } else { st[cnt++]='c'; s2.push(a[i]); } while(!s1.empty()&&s1.top()==num||!s2.empty()&&s2.top()==num) { if(!s1.empty()&&s1.top()==num) { s1.pop(); st[cnt++]='b'; num++; } if(!s2.empty()&&s2.top()==num) { s2.pop(); st[cnt++]='d'; num++; } } } for (int i=0;i<cnt-1;i++) //输出 printf("%c ",st[i]); printf("%c\n",st[cnt-1]); } //system("pause"); return 0;
}
首先声明,上面的代码绝对是可以AC的,但是还是有点问题。。。
我们输入:10 4 6 3 2 1 5 10 9 7 8 这组数据试试看,得到下面的输出:
a c a a a b b b b a b d a a a b a b b b
看看上面打下划线的部分,d是栈2的操作,a是栈1的操作,两种操作互不影响,属于无关操作,那么根据字典序最小的原则,则应该改为 a a a d
这样代码就得改了,栈模拟的顺序需要一点点小改动,可是本人很懒,所以直接在输出前加上这段代码:
for(int i=0;i<cnt-1;i++) if(st[i]=='d'&&st[i+1]=='a') swap(st[i],st[i+1]);
这样输出变成:a c a a a b b b b a b a a a d b a b b b ( bingo ! )
b和c操作之间并不存在这种情况,所以不用考虑了……
至此,我们成功地水过了这道题,有什么问题,欢迎大家指教!
posted on 2014-10-23 09:31 slowly stronger 阅读(955) 评论(0) 编辑 收藏 举报