P1155 雙棧排序(二分圖染色)


P1155 雙棧排序(二分圖染色)

題目描述

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:  復制
4
1 3 2 4
輸出樣例#1:  復制
a b a a b b a b
輸入樣例#2:  復制
4
2 3 4 1
輸出樣例#2:  復制
0
輸入樣例#3:  復制
3
2 3 1
輸出樣例#3:  復制
a c a b b d

說明

30%的數據滿足: n<=10

50%的數據滿足: n<=50

100%的數據滿足: n<=1000

 

題解一:

二分圖染色+模擬 
1.首先考慮一個簡單情況——單棧排序,顯然有這樣的一個事實: 

a[i]和a[j] 不能壓入同一個棧⇔存在一個k,使得i < j < k且a[k] < a[i] < a[j] 

對應變量

I

J

K

 

下標

1

2

3

4

a[]

2

3

1

4

顯然上面的i,j,k滿足關系。

顯然上面這個序列不能滿足只用一個棧操作這個要求。
時間復雜度為O(n^3).對於n<=1000仍顯吃力,對此可以用動態規划的思想,將上述復雜度降到O(n^2)。 
狀態:f[i]=min(a[i],a[i+1], … ,a[n]) 
邊界條件:f[n+1]=INF; 
狀態轉移方程:f[i]=min(f[i+1],a[i]); 
於是上述判斷就轉化為了f[j+1] < a[i] && a[i] < a[j] 
2.擴展到雙棧排序: 
如果a[i]和a[j]不能在一個棧內,即連接一條i與j之間的無向邊,接下來我們只需要判斷這個圖是否為二分圖 
由於題目中說編號的字典序要盡可能的小,那么就把編號小的盡可能放到stack1 
判斷二分圖的方法可以采用黑白染色的方式,先從編號小的開始染,第一個頂點染成黑色,相鄰的頂點染成不同的顏色,如果發現黑白沖突,那么說明這個圖不是一個二分圖,是不合法的,輸出0. 
(DFS或BFS染色均可) 
3.染色后所有黑色的點進stack1,所有白色的點進stack2,最后模擬輸出過程就可以了.

 

 

分析:首先,元素要么用一個棧排序,要么用兩個棧排序,如果用一個棧排序,那么字典序可以保證最小,為什么要兩個棧呢?因為會存在元素f(i),f(j)不能在一個棧里面排序.什么樣的元素不能在同一個棧里面排序呢?當f(i) < f(j) f(i) > f(k),且i < j < k時不行,首先k必須要第一個彈出,因為j > i,在f(k)彈出之前f(i)和f(j)都在棧里面,而f(k)彈出之后f(j) > f(i),而f(j)在棧頂,所以不行.根據這個,我們可以把元素分到兩個棧里去排序.可以把兩個棧看作一個二分圖,可以知道一個棧里面的點不能和棧里另一個點相連,如果滿足二分圖,那么就可以排序.怎么檢測是不是二分圖呢?把不能在一個棧里面排序的元素連邊,給其中一個元素染色,另一個染不同的顏色,如果一個元素相連的顏色和自己相同則不是二分圖.我們在染色的時候把最小的顏色染成最小的.這樣在排序的時候就可以滿足字典序.然后一個一個掃描,模擬排序即可.

 

 1 #include<cstdio>
 2 #include<cstring>
 3 #include<iostream>
 4 #include<algorithm>
 5 #define Ll long long
 6 using namespace std;
 7 int a[1001],v[1001],f[1001];
 8 int q1[1001],top1,q2[1001],top2,now,l;
 9 bool ok[1001][1001];
10 int n;
11 void gg(){printf("0");exit(0);}
12 void dfs(int x,int y)
13 {
14     v[x]=y;
15     for(int i=1;i<=n;i++)
16         if(ok[i][x])
17             if(v[i]==y)gg();else
18             if(v[i]==0)dfs(i,y^1);
19 }
20 int main()
21 {
22     scanf("%d",&n);
23     for(int i=1;i<=n;i++)scanf("%d",&a[i]);
24     f[n+1]=n+1;
25     for(int i=n;i;i--)f[i]=min(f[i+1],a[i]);
26     for(int i=1;i<=n;i++)
27         for(int j=i+1;j<=n;j++)
28             if(a[i]<a[j]&&a[i]>f[j])ok[i][j]=ok[j][i]=1;
29     for(int i=1;i<=n;i++)if(v[i]==0)dfs(i,2);
30     l=1;now=1;a[0]=n+1;
31     while(now<=n)
32         if(v[l]==2&&a[q1[top1]]>a[l])printf("a "),q1[++top1]=l++;else
33         if(now==a[q1[top1]])printf("b "),top1--,now++;else
34         if(v[l]==3&&a[q2[top2]]>a[l])printf("c "),q2[++top2]=l++;else
35         if(now==a[q2[top2]])printf("d "),top2--,now++;
36 }

 

可以用貪心或者二分圖染色解決

下面討論貪心的做法

首先我們來討論單棧排序的性質.

很顯然有如下三個性質:

  1. 棧頂元素一定要大於棧內元素

  2. 給定序列必須從左至右加入棧, 且只能加入一個棧(只有一個棧)

  3. 出棧時, 必須最小的先出棧, 然后大的才能出棧

假如我們不考慮當前已經出棧的元素, 根據3條件, 若當前最小值還未入棧, 那么這個棧一定不能彈, 只能一直加, 直到最小值入棧, 那么在這個過程中, 就有可能出現沖突(與1條件沖突). 用公式表達就是 a3 < a1 < a2a3<a1<a2, 如果出現這種情況就說明無解(單棧排序只有進棧和出棧兩種選擇, 所以沒有多種方案).

那么再把單棧排序推廣到雙棧排序, 唯一的變數就是可以選擇加入哪個棧了, 在單棧排序中會GG的 a3 < a1 < a2a3<a1<a2的情況也不會GG了, 考慮為什么不會GG了, 通過思考可以發現, 因為第二個棧可以容納一個a2a2 所以可以多一條命, 那么如果出現a4 < a1 < a2 < a3a4<a1<a2<a3 的情況, 即使多一個棧也要GG.

不妨設s1s1為主棧, s2s2為輔棧(優先加入s1s1, 因為這樣字典序小),

同過題目描述可知, 優先級關系如下:

  1. 加入s1s1

  2. s1s1彈出

  3. 加入s2s2

  4. s2s2彈出

我們按照這樣的優先級進行每一個操作(即, 如果能加入s1s1就加入s1s1, 否則再考慮能否從s1s1彈出, 否則再考慮加入s2s2...), 彈棧操作無需過多判斷, 能彈就彈, 這樣一定是最優的, 入棧操作的判斷稍微復雜一點, 我們需要判斷是否可以把元素加入主棧, 假如當前需要考慮是否加入s1s1的元素為aa, 首先就需要判斷它是否滿足1條件, 如果滿足1條件, 對應到上面的公式, 此時的aa應該是a2a2(為什么不是a1a1?,因為已經入棧的元素的序號一定比未入棧的元素的序號小, a2a2和a1a1剛好滿足), 所以我們需要判斷它后面是否存在一個a3a3比它大, 並且還有一個a4a4比它小, 如果存在, 那就說明這個元素應該進入輔棧(當然, 如果你發現它連輔棧都進入不了, 那就真的GG了).判斷一個元素是否進入輔棧只需要判斷這個元素是否滿足條件1(比棧頂元素小), 不需要再像主棧那樣判斷后面是否有.., 為什么? 因為輔棧沒有輔棧, 這不是三棧排序!(即使是也不能這么做), 你可能懷疑得這樣不會錯嗎? 當然不會錯, 因為我們有最強的1條件守護着輔棧, 在這時就像主棧那樣判斷.., 反而會錯, 因為后面的元素不一定是加入輔棧的, 輔棧只能吃主棧剩下的東西, 假如去判斷...,

那么這就是在強制認為這后面的東西是屬於輔棧的, 那肯定是不行的!

 1 #include <cstdio>
 2 #include <cstring>
 3 #include <stack>
 4 using namespace std;
 5 const int N = 1001;
 6 const char s[5] = {'r', 'a', 'b', 'c', 'd'};
 7 int data[N], n;
 8 stack<int> s1;
 9 stack<int> s2;
10 int ans[N * 2], top = 0;
11 int Max[N];
12 
13 bool check(int pos) {
14     if (s2.empty()) return true;
15     int i;
16     for (i = pos + 1; i <= n && (data[i] < data[pos] || data[i] < s2.top()); i++);
17     for (int j = i + 1; j <= n; j++) if (data[j] < data[pos]) return 0;
18     return 1;
19 }
20 
21 int main() {
22   //  freopen("testdata (7).in", "r", stdin);
23     scanf("%d", &n);
24     for (int i = 1; i <= n; i++)
25         scanf("%d", &data[i]);
26 
27     for (int i = n; i >= 1; i--) {
28         Max[i] = max(Max[i + 1], data[i]);
29     }
30 
31     bool flag = true;
32     int pos = 1, now = 1;
33     while(pos <= n) {
34         switch(1) {
35             case 1:
36                 if ( (s1.empty() || s1.top() > data[pos]) && check(pos)) {
37                     s1.push(data[pos]);
38                     ans[++top] = 1;
39                     pos++;
40                     break;
41                 }
42             case 2:
43                 if (s1.empty() == 0 && s1.top() == now) {
44                     ans[++top] = 2;
45                     now++;
46                     s1.pop();
47                     break;
48                 }
49             case 3:
50                 if ((s2.empty() || s2.top() > data[pos])) {
51                     s2.push(data[pos]);
52                     ans[++top] = 3;
53                     pos++;
54                     break;
55                 }
56             case 4:
57                 if (s2.empty() == 0 && s2.top() == now) {
58                     ans[++top] = 4;
59                     now++;
60                     s2.pop();
61                     break;
62                 }
63                 flag = false;
64         }
65         if (flag == false) break;
66     }
67     while ((s1.empty() == 0 || s2.empty() == 0) && flag) {
68         if (s1.empty() == 0 && s1.top() == now) {
69             ans[++top] = 2;
70             now++;
71             s1.pop();
72         }
73         if (s2.empty() == 0 && s2.top() == now) {
74             ans[++top] = 4;
75             now++;
76             s2.pop();
77         }
78     }
79 
80     if (flag == 0) printf("0\n");
81     else {
82         for (int i = 1; i <= top; i++) {
83             putchar(s[ans[i]]);
84             putchar(' ');
85         }
86     }
87     return 0;
88 }

 


注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
粤ICP备14056181号  © 2014-2021 ITdaan.com