【问题描述】
用两个栈使一个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 }