NOIP2008 双栈队列
1. 双栈排序
(twostack.pas/c/cpp)
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 希望知道其中字典序最小的操作序列是什么。
【输入】
输入文件 twostack.in 的第一行是一个整数 n。
第二行有 n 个用空格隔开的正整数,构成一个 1~n 的排列。
【输出】
输出文件 twostack.out 共一行,如果输入的排列不是“可双栈排序排列”,输出数字 0;
否则输出字典序最小的操作序列,每两个操作之间用空格隔开,行尾没有空格。
【输入输出样例1】
twostack.in |
twostack.out |
4 1 3 2 4 |
a b a a b b a b |
【输入输出样例2】
twostack.in |
twostack.out |
4 2 3 4 1 |
0 |
【输入输出样例3】
twostack.in |
twostack.out |
3 2 3 1 |
a c a b b d |
【限制】
30%的数据满足: n<=10
50%的数据满足: n<=50
100%的数据满足: n<=1000
【思路】
构造“不可能”边+二分图着色
点的分配转化为了二分图的着色。
(以下摘自洛谷OI【题解】)
考虑对于任意两个数q1[i]和q1[j]来说,它们不能压入同一个栈中的充要条件是什么(注意没有必要使它们同时存在于同一个栈中,只是压入了同一个 栈).实际上,这个条件p是:存在一个k,使得i<j<k且q1[k]<q1[i]<q1[j].
首先证明充分性,即如果满足条件p,那么这两个数一定不能压入同一个栈.这个结论很显然,使用反证法可证.
假设这两个数压入了同一个栈,那么在压入q1[k]的时候栈内情况如下:
…q1[i]…q1[j]…
因为q1[k]比q1[i]和q1[j]都小,所以很显然,当q1[k]没有被弹出的时候,另外两个数也都不能被弹出(否则q2中的数字顺序就不是1,2,3,…,n了).
而之后,无论其它的数字在什么时候被弹出,q1[j]总是会在q1[i]之前弹出.而q1[j]>q1[i],这显然是不正确的.
接下来证明必要性.也就是,如果两个数不可以压入同一个栈,那么它们一定满足条件p.这里我们来证明它的逆否命题,也就是"如果不满足条件p,那么这两个数一定可以压入同一个栈."
不满足条件p有两种情况:一种是对于任意i<j<k且q1[i]<q1[j],q1[k]>q1[i];另一种是对于任意i<j,q1[i]>q1[j].
第一种情况下,很显然,在q1[k]被压入栈的时候,q1[i]已经被弹出栈.那么,q1[k]不会对q1[j]产生任何影响(这里可能有点乱,因为看起 来,当q1[j]<q1[k]的时候,是会有影响的,但实际上,这还需要另一个数r,满足j<k<r且 q1[r]<q1[j]<q1[k],也就是证明充分性的时候所说的情况…而事实上我们现在并不考虑这个r,所以说q1[k]对q1[j]没
有影响).
第二种情况下,我们可以发现这其实就是一个降序序列,所以所有数字都可以压入同一个栈.
这样,原命题的逆否命题得证,所以原命题得证.
此时,条件p为q1[i]和q1[j]不能压入同一个栈的充要条件也得证.
这样,我们对所有的数对(i,j)满足1<=i<j<=n,检查是否存在i<j<k满足p1[k]<p1[i]& lt;p1[j].如果存在,那么在点i和点j之间连一条无向边,表示p1[i]和p1[j]不能压入同一个栈.此时想到了什么?那就是二分图~
二分图的两部分看作两个栈,因为二分图的同一部分内不会出现任何连边,也就相当于不能压入同一个栈的所有结点都分到了两个栈中.
此时我们只考虑检查是否有解,所以只要o(n)检查出这个图是不是二分图,就可以得知是否有解.
然后处理最小字典序问题。实际上对二分图染色后对应的操作显然编号小的优先使用a操作,可以使得字典序尽量小。
这里要提二分图的一个性质:二分图中不同的连通分量染色是互不影响的。所以为了满足最小字典序的问题,可以选取编号最小的未染色结点染成1并对它所在连通分量染色。染完之后,模拟输出序列就可以了。
【代码】
1 #include<iostream> 2 #include<cstdlib> 3 #include<vector> 4 #include<stack> 5 using namespace std; 6 const int maxn = 1000+10; 7 8 int S[maxn],C[maxn]; 9 vector<int> G[maxn]; 10 int n; 11 12 void dfs(int u,int c){ 13 C[u]=c; 14 for(int i=0;i<G[u].size();i++){ 15 int v=G[u][i]; 16 if(C[v]==C[u]) { 17 cout<<0; exit(0); 18 } 19 else if(!C[v]) dfs(v,3-c); 20 } 21 } 22 int main() { 23 ios::sync_with_stdio(false); 24 25 cin>>n; 26 for(int i=1;i<=n;i++) cin>>S[i]; 27 int _min[maxn]; _min[n+1]=1<<30; 28 for(int i=n;i;i--) _min[i]=min(_min[i+1],S[i]); 29 for(int i=1;i<n;i++) 30 for(int j=i+1;j<=n;j++) if(S[i]<S[j] && _min[j+1]<S[i]) { //i<j<k && (S[k]<S[i]<S[j]) 31 G[i].push_back(j); G[j].push_back(i); 32 } 33 34 for(int i=1;i<=n;i++) if(!C[i]) dfs(i,1); 35 36 int aim=1; 37 stack<int> sta,stb; 38 for(int i=1;i<=n;i++) { 39 if(C[i]==1) { 40 sta.push(S[i]); cout<<"a "; 41 } 42 else { 43 stb.push(S[i]); cout<<"c "; 44 } 45 while(!sta.empty()&&sta.top()==aim || !stb.empty()&&stb.top()==aim) { 46 if(!sta.empty()&&sta.top()==aim){ 47 sta.pop(); cout<<"b "; 48 } 49 else { 50 stb.pop(); cout<<"d "; 51 } 52 aim++; 53 } 54 } 55 return 0; 56 }
posted on 2015-10-07 20:54 hahalidaxin 阅读(207) 评论(0) 编辑 收藏 举报