1. 程式人生 > 其它 >AT1984 [AGC001F] Wide Swap(特殊處理+歸併排序)

AT1984 [AGC001F] Wide Swap(特殊處理+歸併排序)

原題連結

題意

給定一個 \(1 \sim n\) 的排列 \(P\)\(P_i\)\(P_j\)\(1 \leq i < j \leq n\))可以交換,當且僅當 \(i\)\(j\) 滿足 \(j-i \geq K(1 \leq K \leq n-1)\),且 \(|P_i-P_j|==1\)。求所有可能的排列中字典序最小的排列。

資料範圍

\(2 \leq n \leq 500000\)

思路

首先注意到題意中的 \(|P_i-P_j|==1\) 有些特殊。於是就可以考慮新定義一個排列 \(B\),滿足 \(B_{P_i}=i\) (就是互換了 \(P\) 中的下標和數值)。那麼原排列中的限制條件在新的排列中就表現為,只能交換兩個相鄰

差值大於等於 \(K\) 的元素。

注意到此時 \(B_i=x\) 的含義是 \(P\) 中下標為 \(x\) 的數的值是 \(i\),那麼要使 \(P\) 序列的字典序最小,就等價於使 \(B\) 的字典序最小。

考慮直接交換相鄰的數,就類似於氣泡排序,可以用歸併排序優化這個過程。對於當前的 \(B_i\)\(B_j\),若 \(B_j\) 能轉移到 \(B_i\) 前面,當且僅當 \(B_i \sim B_{mid}\) 中最小的元素 \(min_{B_i} \geq B_j+K\)。否則就只能移動到第一個滿足 \(min_{B_i} \geq B_i+K\) 的數的前面。

最後再把 \(B\)

排列還原回 \(P\) 中,方法和起初 \(P\) 構造 \(B\) 的方法一樣。時間複雜度 \(O(n \log n)\)

code:

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int N=5e5+10;
const int INF=0x3f3f3f3f;
int K,minn[N],a[N],b[N],n,t[N];
void merge_sort(int l,int r)
{
	if(l==r) return ;
	int mid=l+r>>1,i=l,j=mid+1,k=l;
	merge_sort(l,mid),merge_sort(mid+1,r);
	minn[mid+1]=INF;for(int i=mid;i>=l;i--) minn[i]=min(minn[i+1],b[i]);
	while(i<=mid&&j<=r)
	    if(minn[i]-b[j]>=K) t[k++]=b[j++];
	    else t[k++]=b[i++];
	while(i<=mid) t[k++]=b[i++];
	while(j<=r) t[k++]=b[j++];
	for(int i=l;i<=r;i++) b[i]=t[i];
}
int main()
{
//	freopen("AKnlc.in","r",stdin);
	scanf("%d%d",&n,&K);
	for(int i=1;i<=n;i++) scanf("%d",&a[i]),b[a[i]]=i;
	merge_sort(1,n);
	for(int i=1;i<=n;i++) a[b[i]]=i;
	for(int i=1;i<=n;i++) printf("%d\n",a[i]);
	return 0;
}