NOIP2008 双栈队列

来源:互联网 发布:室内设计作图软件 编辑:程序博客网 时间:2024/04/26 00:56

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 }

 

0 0
原创粉丝点击