--- titile: Two Stack Sorting --- # Two Stacks Sorting 題解與想法 --- ## 題敘 來源: [cses](https://cses.fi/problemset/task/2402) --- >給定 $1$ ~ $n$ 的排列 $a_1$~$a_n$,每次可以做以下一個操作: > >($b$初始為空,$S_1$、$S_2$為兩相異stack) >* 將$a$陣列最前面的數字push進$S_1$,並將$a$陣列第一項刪除 >* 將$a$陣列最前面的數字push進$S_2$,並將$a$陣列第一項刪除 >* 將$S_1.top$加入$b$陣列末尾,並$S_1.pop()$ >* 將$S_2.top$加入$b$陣列末尾,並$S_2.pop()$ > >請問是否能使 $1 \leq i \leq n$ , $b_i=i$ ? >如果不行請輸出 "IMPOSIBBLE" >如果可以請輸出 $1 \leq i \leq n$ , $a_i$ 被分配到哪個stack --- ## 提示 ### Hint 1 要使$b$由小到大遞增,stack要維護什麼性質 ### Hint 1.5 如果想的是greedy可以想想反例 ### Hint 2 一個數什麼時候會離開stack ### Hint 3 兩個數在什麼條件下不能在同一個stack ### Hint 4 兩個數在什麼條件下必須在同一個stack --- ## 題解 首先我們可以觀察到stack由下到上一定是遞減的 :::spoiler proof > 假設$c_1$在$c_2$上面 > 可知在$b$中$c_1$一定在$c_2$前面 > 由於$b$是遞增的 > 可知$c_1$<$c_2$ ::: 而再觀察題目一下會發現,所有數字從放入stack到拿出stack會是一段時間區間 而時間區間為 : ($.in$為進入時間 $.out$為跳出時間 $n.in$為$n$在$a$中的位置) $$ \begin{cases} \large{n.out=(n-1).out}\quad\quad\quad(n.in<(n-1).out)\\ \large{n.out=n.in}\quad\quad\quad\quad\quad\quad(n.in>(n-1).out) \end{cases} $$ 特別地 定義 $1.out$=$1.in$ :::spoiler proof >如果$n$要pop到$b$的尾端,顯然$b$的尾端為$n-1$ > >而如果$n.in$<$(n-1).out$ >在$(n-1)$被pop時$n$為stack中的最小值,所以可以同時pop > >如果$n.in$>$(n-1).out$ >n進入stack時顯然為最小值,可以直接pop ::: 經過這個變換可以將測資 ``` 5 2 4 1 6 3 ``` 轉換為![541263](https://hackmd.io/_uploads/SkxyVUwBa.png) 又我們可以觀察到問題能轉換為 找到一個分組方式: $\forall x \in a$ , ($x\in S_1 \lor x\in S_2)$ $\forall x \in S_1$ , $\mathop{\land}\limits_{y\in S_1 }$(( $x\geq y$ ) $\lor$ ($x.in>y.in$) $\lor$ ($y.in > x.out$)) $\forall x \in S_2$ , $\mathop{\land}\limits_{y\in S_2 }$(( $x\geq y$ ) $\lor$ ($x.in>y.in$) $\lor$ ($y.in > x.out$)) 白話文就是強迫分兩組且組內不相交 :::spoiler proof >強迫分組顯然 > >如果兩時間相交,不失一般性假設 $l.in<r.in<l.out<r.out$ >假設$l$,$r$同組 >因為$l.in<r.in$所以$l$在$r$之下 >所以$l.out$應該要大於$r.out$ >與假設不符 >所以$l,r$不同組 ::: 這時我們就有$O(n^2logn)$作法: ``` 由1~n枚舉i 對每個i枚舉i+1~n 如果相交就有一組兩兩不再同一組的條件 用經典的2-sat分組特例作法 就可以用disjointset解掉 ``` :::spoiler code ``` cpp= #include <bits/stdc++.h> #define pii pair<int,int> #define ff first #define ss second #define inv(x) (x<n?x+n:x-n) using namespace std; const int mx=2e5+5; int p[mx<<1]; int n; int c[mx]; int a[mx]; int pl[mx]; pii pr[mx]; void init(){ for (int i=1;i<=2*n;i++){ p[i]=i; } } int find(int x){ for (int i=0;i<1000000;i++){} return x==p[x]?x:p[x]=find(p[x]); } void join(int x,int y){ x=find(x),y=find(y); if (x!=y){ p[x]=y; } } bool mk(){ for (int i=1;i<=n;i++){ if (find(p[i])==find(p[i+n])){ return 0; } } int t1=find(1),t2=find(1+n); for (int i=1;i<=n;i++){ int fi=find(i); if (find(fi)==t1){ c[i]=1; }else if (t2==find(fi)){ c[i]=2; }else{ p[fi]=t1; p[find(inv(fi))]=t2; c[i]=1; } } return 1; } int main(){ cin.tie(0);ios::sync_with_stdio(0); cin >> n; init(); for (int i=1;i<=n;i++){ cin >> a[i]; pl[a[i]]=i; } pr[1]={pl[1],pl[1]}; for (int i=2;i<=n;i++){ if (pl[i]>pr[i-1].ss){ pr[i]={pl[i],pl[i]}; }else{ pr[i]={pl[i],pr[i-1].ss}; } } for (int i=1;i<n;i++){ for (int j=i+1;j<=n;j++){ if ((pr[i].ff<pr[j].ff&&pr[j].ff<pr[i].ss)||(pr[i].ff<pr[j].ss&&pr[j].ss<pr[i].ss)){ p[find(i+n)]=find(j); p[find(j+n)]=find(i); } } } if (!mk()){ cout << "IMPOSSIBLE\n"; }else{ for (int i=1;i<=n;i++){ cout << c[a[i]] << " "; } cout << "\n"; } } ``` ::: ### 最終版 但看看測資範圍 $n\leq2e5$ $O(n^2logn)$ 的作法顯然太暴力 我們要怎麼有效率的做呢 思考有什麼條件會使兩數同組,還有什麼時候一定"IMPOSSIBLE" 回到前面將圖畫出,~~瞪個一個禮拜,再被段考慘電~~ 可以發現如果兩線段相交且相交區有還未被左界訪問過的點一定"IMPOSSIBLE" 如果兩線段有包含關係且重和區有位被左界拜訪的點,兩線段必須同組 所以我們可以~~通靈~~推論出以下作法: ``` 維護一個存不相交線段的set 在每次枚舉時當作將線段insert進set 用lower_bwound找出最小的右界大於插入線段的左界 如果與第一個有相交 查詢相交區是否都被訪問過 (我用BIT 感覺可壓(?) 並刪除原本線段與新線段相交處 一路枚舉到set尾端 對於後面的線段查詢重和部分是否都被訪問過 如果還沒 將新線段與原線段放入同一組 刪除訪問到的線段 最後插入原線段 ``` :::spoiler proof > 如果兩線段重和且中間還有未被訪問的點 > 假設三線段分別為$l_a$ 、 $l_b$ 、 $l_c$ > 因為$l_c.in<l_a.out$所以 $l_c$ 不同組於 $l_a$ > 因為$l_c.in<l_b.out$所以 $l_c$ 不同組於 $l_b$ > 所以$l_a$同組於$l_b$ ::: :::spoiler AC code (16ms) ``` cpp= #include <bits/stdc++.h> #define pii pair<int,int> #define ff first #define ss second #define inv(x) (x>n?x-n:x+n) #pragma GCC optmize("O3") using namespace std; const int mx=2e5+5; int p[mx<<1]; int n; int c[mx]; int a[mx]; int pl[mx]; pii pr[mx]; void init(){ for (int i=1;i<=2*n;i++){ p[i]=i; } } int find(int x){ for (int i=0;i<1000000;i++){} return x==p[x]?x:p[x]=find(p[x]); } void join(int x,int y){ x=find(x),y=find(y); if (x!=y){ p[x]=y; } } bool mk(){ for (int i=1;i<=n;i++){ if (find(p[i])==find(p[i+n])){ return 0; } } int t1=find(1),t2=find(1+n); for (int i=1;i<=n;i++){ int fi=find(i); if (find(fi)==t1){ c[i]=1; }else if (t2==find(fi)){ c[i]=2; }else{ p[fi]=t1; p[find(inv(fi))]=t2; c[i]=1; } } return 1; } int bit[mx]; void insert(int id){ for (int i=id;i<mx;i+=(i&-i)){ bit[i]+=1; } } int qur(int l,int r){ int sl=0;int sr=0; for (int i=l-1;i>0;i-=i&-i){ sl+=bit[i]; } for (int i=r;i>0;i-=i&-i){ sr+=bit[i]; } return sr-sl; } struct pi{ int l,r; int id; void init(int a,int b,int c){ l=a; r=b; id=c; } bool operator<(pi b){ return r<b.r; } }; bool operator<(pi a,pi b){ return a.r<b.r; } set<pi> st; void tdo(){ for (int i=1;i<=n;i++){ insert(pr[i].ff); pi pa; pa.init(0,pr[i].ff,0); auto it=st.lower_bound(pa); if (it==st.end()){ pi pa; pa.init(pr[i].ff,pr[i].ss,i); st.insert(pa); }else{ if ((*it).l<pr[i].ff){ int ns=qur(pr[i].ff,pr[i].ss); if (ns<(*it).r-pr[i].ff+1){ cout << "IMPOSSIBLE\n"; exit(0); } join((*it).id,i+n); join((*it).id+n,i); pi pa; pa.init((*it).l,pr[i].ff,(*it).id); auto pre=it; it++; st.erase(pre); st.insert(pa); } for (auto j=it;j!=st.end();){ if (qur((*j).l,(*j).r)<(*j).r-(*j).l+1){ join((*j).id,i); join((*j).id+n,i+n); } auto pre=j; j++; st.erase(pre); } pi pa; pa.init(pr[i].ff,pr[i].ss,i); st.insert(pa); } } } int main(){ cin.tie(0);ios::sync_with_stdio(0); cin >> n; init(); for (int i=1;i<=n;i++){ cin >> a[i]; pl[a[i]]=i; } pr[1]={pl[1],pl[1]}; for (int i=2;i<=n;i++){ if (pl[i]>pr[i-1].ss){ pr[i]={pl[i],pl[i]}; }else{ pr[i]={pl[i],pr[i-1].ss}; } } tdo(); if (!mk()){ cout << "IMPOSSIBLE\n"; }else{ for (int i=1;i<=n;i++){ cout << c[a[i]] << " "; } cout << "\n"; } } ``` ::: ### 複雜度分析: >每個線段最多被刪一次,且刪除所產生的線段最多$n$個 >每次刪除會做一次lower_bound 花 $(log n)$ $\times$ $n$ >每次被刪會做一次find和join 花 $(log n)$ $\times$ $n$ >所以時間複雜度為$O(nlogn)$ ---- ## 致謝 我要感謝那些我參考過~~但看不懂~~的題目想法 >[codeforce ashwanth106121023](https://codeforces.com/blog/entry/122535) [JOISC - Port Facility: Bicoloring & Segment Trees](https://mamnoonsiam.github.io/posts/joisc-2017-port-facility) 還有niter學長和我一起討論 還有 [cses 題源](https://cses.fi/problemset/task/2402/) --- ## 後語 欸我好像忘記說右界會單調遞增了 ~~阿隨便拉顯而易見~~ <- 可以看看定義式就可以推出來 還有人家都寫線段樹我不會qwq