ARC165F題解

Nekopedia發表於2024-10-23

前言

\(2024.10.19\) 日校測 \(T4\),思維太廟,被薄紗了,遂哭弱,寫題解以記之。

簡要題意

給你一個長度為 \(2n\) 的序列 \(A,\forall a_i\in[1,n]\),其中 \(1\)\(n\) 每個數都出現了兩次,現在需要把相同的兩個數排到一起,每次操作只能交換相鄰兩個數,在保證操作次數最小的情況下求出現在序列的最小字典序

資料範圍:\(1\le n\le2\times10^5\)

思路

做題的時候首先應該考慮題目性質,可以從手玩樣例開始。因為最後並沒有讓你求最少操作次數,所以我們只用討論數與數之間的關係。我們考慮最簡單的情況:假設現在序列中只有 \(1\)\(2\) 各兩個,一共存在六種可能的情況。我們先將他們列出來:

  1. \(1122\)
  2. \(1212\)
  3. \(1221\)
  4. \(2112\)
  5. \(2121\)
  6. \(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;
}