前言
\(2024.10.19\) 日校測 \(T4\),思維太廟,被薄紗了,遂哭弱,寫題解以記之。
簡要題意
給你一個長度為 \(2n\) 的序列 \(A,\forall a_i\in[1,n]\),其中 \(1\) 到 \(n\) 每個數都出現了兩次,現在需要把相同的兩個數排到一起,每次操作只能交換相鄰兩個數,在保證操作次數最小的情況下求出現在序列的最小字典序。
資料範圍:\(1\le n\le2\times10^5\)。
思路
做題的時候首先應該考慮題目性質,可以從手玩樣例開始。因為最後並沒有讓你求最少操作次數,所以我們只用討論數與數之間的關係。我們考慮最簡單的情況:假設現在序列中只有 \(1\) 和 \(2\) 各兩個,一共存在六種可能的情況。我們先將他們列出來:
- \(1122\)
- \(1212\)
- \(1221\)
- \(2112\)
- \(2121\)
- \(2211\)
對於第一種和第六種情況我們可以不用考慮,因為需要保證運算元最小。然後這四種情況實際只有兩種本質不同,我們將他們抓出來。
- \(ABAB\)
- \(ABBA\)
對於第一個情況,我們只需將中間兩數交換即可。而第二種,我們既可以將第一個數交換到第三個位置,也可以將最後一個數交換到第二個位置。也就是說:第一種情況下數的位置決定最後順序;而第二種情況下數的大小決定了最後順序。
現在考慮擴充套件這兩種情況,對於數列中任意的兩數 \(A,B\),如果滿足 \(A\dots B\dots A\dots B\) 的形式,我一定會讓 \(A\) 排在 \(B\) 前面;如果滿足 \(A\dots B\dots B\dots A\) 的形式,我就會去考慮兩個數之間的大小關係。
總結一下:
\(\forall x\in[1,n]\),設 \(a_x\) 表示其第一次出現的位置,\(b_x\) 表示第二次出現的位置,如滿足偏序:\(a_i\le a_j,b_i\le b_j\) 則 \(i\) 在 \(j\) 之前。所以把這些偏序抽象成一張圖跑拓撲排序,拓撲時讓數字小的點儘量先跑就能滿足第二種情況。
可是直接建圖跑是 \(O(n^2)\) 的,考慮最佳化。我們把每一個關於 \(i\) 的二元組 \((a_i,b_i)\) 看成平面內的點,若 \(i,j\) 之間連邊則需要滿足上述偏序。我們可以考慮分治建圖,也就是類似 \(\text{cdq}\) 的過程,具體見下圖:
我們橫著切一刀把平面分成兩部分,在分割線上建一些虛點。對於下面的實點垂直向上連邊,上面的實點從下面虛點往上連邊,然後虛點之間從左往右連。若每次在中間切最多切出 \(\log n\) 層,所以只有 \(n\log n\) 個點和邊。但是拓撲的時候如果用優先佇列是兩隻 \(\log\) 的,考慮繼續最佳化。
其實我們只需要對實點用優先佇列,對於虛點我們不關心他們的具體順序,所以開一個普通佇列存虛點,另一個優先佇列存實點,每次先把所有普通佇列的點拓撲完再去拓撲優先佇列就行。時間複雜度是 \(O(n\log n)\) 的,因為實點只有 \(n\) 個。
程式碼
void cdq(int l, int r){
if(l == r)return; int mid = l + r >> 1, lim = a[mid].l;
cdq(l, mid), cdq(mid + 1, r);
int i = l, j = mid + 1, k = l;
while(i <= mid and j <= r)a[i].r < a[j].r ? b[k++] = a[i++] : b[k++] = a[j++];
while(i <= mid)b[k++] = a[i++]; while(j <= r)b[k++] = a[j++];
for(int i = l; i <= r; ++i){
a[i] = b[i], ++nd; if(i ^ l)e[nd - 1].pb(nd), ++in[nd];
if(a[i].l <= lim)e[a[i].id].pb(nd), ++in[nd];
else e[nd].pb(a[i].id), ++in[a[i].id];
}
}
void upd(int x){
if(in[x])return;
x <= n ? q.push(x) : qc.push(x);
}
signed main(){
freopen("swap.in", "r", stdin);
freopen("swap.out", "w", stdout);
n = rd(), nd = n << 1;
for(int i = 1; i <= nd; ++i){
int x = rd();
if(a[x].l)a[x].r = i; else a[x].l = i, a[x].id = x;
}
sort(a + 1, a + 1 + n); nd = n;
cdq(1, n);
for(int i = 1; i <= nd; ++i)upd(i);
while(! q.empty() or ! qc.empty()){
while(! qc.empty()){
int u = qc.front(); qc.pop();
for(int v : e[u])--in[v], upd(v);
}
if(q.empty())return 0;
int u = q.top(); q.pop();
pc(u), putchar(' '), pc(u), putchar(' ');
for(int v : e[u])--in[v], upd(v);
}
return 0;
}