\(tom\)最近在研究乙個有趣的排序問題。如圖所示,通過\(2\)個棧\(s_1\)和\(s_2\),\(tom\)希望借助以下\(4\)種操作實現將輸入序列公升序排序。
操作\(a\)
如果輸入序列不為空,將第乙個元素壓入棧\(s_1\)
操作\(b\)
如果棧\(s_1\)不為空,將\(s_1\)棧頂元素彈出至輸出序列
操作\(c\)
如果輸入序列不為空,將第乙個元素壓入棧\(s_2\)
操作\(d\)
如果棧\(s_2\)不為空,將\(s_2\)棧頂元素彈出至輸出序列
如果乙個$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$
輸入的第一行是乙個整數n。
第二行有$n$個用空格隔開的正整數,構成乙個$1$到$n$的排列。
輸出共一行,如果輸入的排列不是「可雙棧排序排列」,輸出數字$0$;否則輸出字典序最小的操作序列,每兩個操作之間用空格隔開,行尾沒有空格。
當然,這樣的操作序列有可能有幾個,對於上例$(1,3,2,4)$,$a,c,c,b,a,d,d,b$是另外乙個可行的操作序列。$tom$希望知道其中字典序最小的操作序列是什麼。
把以前不會寫的$noip$題目補一下~其實現在我也還是不會。
感覺這道題思路非常神……首先,貪心的選顯然是錯誤的,因為不能保證可以構造出一組可行解。然後……這道題到底該怎麼做啊啊啊!
於是到最後我還是去看題解了……
這道題首先要證明乙個結論:對於任意兩個數$a_i$和$a_j$來說,它們不能壓入同乙個棧(不僅僅指同時在棧中,而是壓入了同乙個棧)中的充要條件是:存在乙個$k$,使得$i首先證明充分性。由於$a_k然後證明必要性。這個東西比較難證,於是我們可以轉而證明它的逆否命題:若對於任意的$i,那麼他們可以壓入乙個棧。
對於不滿足$a_ka_i$,二是$a_i>a_j$。
對於第一種情況,由於$a_k>a_i$,所以在$a_k$入棧之前$a_i$已經可以彈出,於是可以壓入同乙個棧。
對於第二種情況,顯然不論$a_k$大小如何,都可以先把$a_i$和$a_j$壓入同乙個棧,$a_j$會先於$a_i$彈出。
於是該命題的逆否命題得證,所以原命題得證。
於是,這個問題就變成了二分圖的問題了。把不可以壓入同乙個棧的點連邊,然後判斷乙個圖不是二分圖就無解,否則有解。
然後就是字典序最小的問題了。由於字典序最小需要靠前的數盡量往第乙個棧裡丟,所以可以從前到後判斷一下,在二分圖中$dfs$一遍。
於是這道題就這麼愉快的解決辣!話說考場上碰到這種題我真的寫得出來嗎?
小結一下:1.碰到一類毫無思路的問題時,要推一推題目中有沒有什麼奇妙的性質;
2.發現自己的複雜度遠過低時,一定要多檢查幾遍;
3.一定要先寫暴力,可以用來驗證自己猜的結論。
下面貼**:
#include#include#include#include#include#define file(s) freopen(s".in","r",stdin),freopen(s".out","w",stdout)#define n 1010
using namespace std;
typedef long long llg;
int n,a[n<<1],wa[n],co[n],b[n],lb;
int head[n],next[n*n],to[n*n],tt;
int s1[n],t1,s2[n],t2,now=1;
bool vis[n];
int getint()
void link(int x,int y)
bool ran(int u)
void dfs(int u,bool w)
int main()
}for(int i=1;i<=n;i++)
if(!vis[i]) dfs(i,co[i]!=1);
for(int i=1;i<=n;i++)
}for(int i=1;i<=lb;i++) printf("%c ",'a'+b[i]-1);
}
upd:最近發現被hack了……於是就重寫了乙份,**如下:
#include#include#include#include#include#define file(s) freopen(s".in","r",stdin),freopen(s".out","w",stdout)#define n 1010
using namespace std;
typedef long long llg;
int n,a[n<<1],wa[n],co[n],b[n],lb;
int hd[n],nt[n*n],to[n*n],tt;
int s1[n],t1,s2[n],t2,now=1;
bool vis[n];
int getint()
void link(int x,int y)
void ran(int u)
else if(!co[v]) co[v]=3-co[u],ran(v);
}int main()
s1[++t1]=a[i],b[++lb]=1;
} if(co[i]==2)
s2[++t2]=a[i]; b[++lb]=3;
} while(s1[t1]==now) t1--,b[++lb]=2,now++;
} while(s1[t1]==now || s2[t2]==now)
for(int i=1;i<=lb;i++) printf("%c ",'a'+b[i]-1);
}
NOIP2008 雙棧排序
link 考慮什麼時候 i,j i,j i,j 可以被放到同乙個棧裡面 我們只需要考慮如果我們把 i,j i,j i,j 放一起後面會不會出現矛盾 假設有 i j i j k,觀察每一種排列,只有當 a k a kak 的時候,是會出現問題的,我們不能構造一種方案滿足這個條件 所以我們通過計算字尾最...
noip 2008 雙棧排序
題目大意 給定n和一串數字,這串數字是乙個1 n的排列。現在要用兩個棧給這些數字排序。首先先判斷是否有解,有解的話再輸出字典序最小的方案 入棧1,輸出a,出棧1,輸出b 入棧2,輸出c,出棧2,輸出d 分析 首先必然要先考慮是否有解。對於沒有解的情況,必然是當到了某乙個數x0時,棧1,棧2隊首元素都...
noip 2008 雙棧排序
題目大意 給定n和一串數字,這串數字是乙個1 n的排列。現在要用兩個棧給這些數字排序。首先先判斷是否有解,有解的話再輸出字典序最小的方案 入棧1,輸出a,出棧1,輸出b 入棧2,輸出c,出棧2,輸出d 分析 首先必然要先考慮是否有解。對於沒有解的情況,必然是當到了某乙個數x0時,棧1,棧2隊首元素都...