noip2008双栈序列(双栈排序)

双栈序列题解

    这题,官方解题报告(其实我也不知道是不是官方的,据说是出题者写的)已经写的相当清楚了,模型是二分图,就不多说了,直接看程序吧。

View Code
 1 var i,j,n,m,k,l:longint;
2 top1,top2:longint;
3 s1,s2:array[0..1000]of longint;
4 a,b:array[0..1000]of longint;
5 color:array[0..1000]of longint;
6 p:array[0..1000,0..1000]of boolean;
7 bo:boolean=true;
8 function dfs(x:longint):boolean;
9 var i,j:longint;
10 begin
11 dfs:=true;
12 for i:=1 to n do
13 if (p[x,i]) then
14 if (color[i]=-1) then
15 begin
16 color[i]:=1-color[x];
17 dfs:=dfs and dfs(i);
18 end else
19 if color[i]+color[x]<>1 then exit(false);
20 end;
21 begin
22 assign(input,'twostack.in');reset(input);
23 assign(output,'twostack.out');rewrite(output);
24 readln(n);
25 for i:=1 to n do
26 read(a[i]);
27 b[n]:=a[n];
28 for i:=n-1 downto 1 do
29 if a[i]<b[i+1] then b[i]:=a[i] else b[i]:=b[i+1];
30 fillchar(p,sizeof(P),false);
31 for i:=1 to n do
32 for j:=i+1 to n-1 do
33 if (b[j+1]<a[i])and(a[i]<a[j]) then begin p[i,j]:=true;p[j,i]:=true;end;
34 fillchar(color,sizeof(color),255);
35 for i:=1 to n do
36 if color[i]=-1 then begin color[i]:=0;if not dfs(i) then begin bo:=false;break;end;end;
37
38 if not bo then begin writeln(0);close(input);close(output);halt;end;
39
40 k:=1;top1:=0;top2:=0;
41 for i:=1 to n do
42 begin
43 if color[i]<>1 then
44 begin
45 inc(top1);
46 s1[top1]:=a[i];
47 write('a ');
48 end else
49 begin
50 inc(top2);
51 s2[top2]:=a[i];
52 write('c ');
53 end;
54 while ((s1[top1]=k)and(top1>0))or((s2[top2]=k)and(top2>0)) do
55 if ((s1[top1]=k)and(top1>0))then
56 begin
57 write('b ');
58 dec(top1);
59 inc(k);
60 end else
61 begin
62 write('d ');
63 dec(top2);
64 inc(k);
65 end;
66 end;
67 close(input);close(output);
68 end.

    顺便把官方题解拷过来。

 

这道题大概可以归结为如下题意:
有两个队列和两个栈,分别命名为队列1(q1),队列2(q2),栈1(s1)和栈2(s2).最初的时候,q2,s1和s2都为空,而q1中有n个数(n<=1000),为1~n的某个排列.
现在支持如下四种操作:
a操作,将 q1的首元素提取出并加入s1的栈顶.
b操作,将s1的栈顶元素弹出并加入q1q2的队列尾.
c操作,将 q1的首元素提取出并加入s2的栈顶.
d操作,将s2的栈顶元素弹出并加入q1q2的队列尾.
请判断,是否可以经过一系列操作之后,使得q2中依次存储着1,2,3,…,n.如果可以,求出字典序最小的一个操作序列.

这道题的错误做法很多,错误做法却能得满分的也很多,这里就不多说了.直接切入正题,就是即将介绍的这个基于二分图的算法.
注意到并没有说基于二分图匹配,因为这个算法和二分图匹配无关.这个算法只是用到了给一个图着色成二分图.

第一步需要解决的问题是,判断是否有解.

考虑对于任意两个数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]<p1[j].如果存在,那么在点i和点j之间连一条无向边,表示p1[i]和p1[j]不能压入同一个栈.此时想到了什么?那就是二分图~
二分图的两部分看作两个栈,因为二分图的同一部分内不会出现任何连边,也就相当于不能压入同一个栈的所有结点都分到了两个栈中.
此时我们只考虑检查是否有解,所以只要O(n)检查出这个图是不是二分图,就可以得知是否有解.

此时,检查有解的问题已经解决.接下来的问题是,如何找到字典序最小的解.
实际上,可以发现,如果把二分图染成1和2两种颜色,那么结点染色为1对应当前结点被压入s1,为2对应被压入s2.为了字典序尽量小,我们希望让编号小的结点优先压入s1.
又发现二分图的不同连通分量之间的染色是互不影响的,所以可以每次选取一个未染色的编号最小的结点,将它染色为1并从它开始DFS染色,直到所有结点都被染色为止.这样,我们就得到了每个结点应该压入哪个栈中.接下来要做的,只不过是模拟之后输出序列啦~

posted @ 2011-10-21 21:18  N_C_Derek  阅读(241)  评论(0)    收藏  举报