CF1830E Bully Sort

神仙题。

对于 \(1\)\(n\) 的排列 \(P\),我们定义一次操作如下:

  • 找到最大的 \(p_i\),使得 \(p_i\) 不等于 \(i\)

  • 找到最小的 \(p_j\),使得 \(i < j\)

  • 交换 \(p_i\)\(p_j\)

定义 \(f(P)\) 为使排列 \(P\) 升序的最少操作次数。进行 \(q\) 次操作,每次操作交换 \(p_x\)\(p_y\),你需要在每次操作后求出 \(f(P)\) 的值。

\(2 \leq n \leq 5 \times 10^5\)\(1 \leq q \leq 5 \times 10^4\)

先考虑单个 \(f(P)\) 的计算,容易发现整个过程相当于对于从大到小的每个 \(p_i\),将其调整到下标为 \(p_i\) 的位置。

不妨令当前操作交换 \(p_i\)\(p_j\),容易发现此时对于 \(x \in (p_i+1,n]\)\(p_x=x\),同时又可知 \(p_i \not= i\),也就是说在当前序列中,若 \(p_x > p_i\),则 \(i < j < x\)。同时由于 \(p_j\) 是满足 \(i<j\) 的元素中最小的一个,所以对于所有 \(p_x < p_j\),均有 \(x < i < j\)。同时我们可以发现 \(p_i > p_j\),则整个序列形如下面的形式:

\[[\dots,p_i,\dots,p_j,\dots,p_i+1,p_i+2,\dots,n] \]

考虑交换 \(p_i\)\(p_j\) 带来的影响。我们考虑如下两个量:

  • \(P\) 的逆序对总数

  • \(\sum\limits_{i=1}^{n} |p_i-i|\)

前者在交换 \(p_i\)\(p_j\) 时,由于 \((p_i,p_j)\) 变为 \((p_j,p_i)\),则逆序对一定减少 \(1\)。我们考虑其他数的贡献。容易发现对于 \([1,p_j)\)\((p_i,n]\) 中的值,其与 \(p_i\)\(p_j\) 的相对位置不变且其下标不在 \((i,j)\) 中,所以不会产生贡献。接下来只考虑满足 \(k \in (i,j)\)\(p_k \in (p_j,p_i)\) 的值即可。容易发现对于所有 \(k \in (i,j)\) 必然满足此条件,而对于这样的每个数都会减少 \(2\) 个逆序对。综合来看,交换 \(p_i\)\(p_j\) 会将逆序对总数减少 \(1+2(j-i-1)=2j-2i-1\)

接下来考虑 \(\sum\limits_{i=1}^{n} |p_i-i|\)。我们首先证明 \(i \geq p_j\)\(j \leq p_i\),这一点是显然的。由于所有 \([1,p_j)\) 中的数下标都在 \([1,i)\) 中且所有 \((p_i,n]\) 中的数下标都在 \((j,n]\) 中,显然会有 \(i \geq p_j\)\(j \leq p_i\)。其次,由 \(i < j\),我们可以推出 \(p_j \leq i < j\)\(p_i \geq j > i\)。我们就有了下面的式子:

  • \(|p_i-i| + |p_j-j| = p_i-i+j-p_j\)

  • \(|p_j-i| + |p_i-j| = i-p_j+p_i-j\)

考虑将 \(p_i\)\(p_j\) 交换,\(\sum\limits_{i=1}^{n} |p_i-i|\) 就会减少 \((p_i-i+j-p_j)-(i-p_j+p_i-j)=2j-2i\)

此时我们可以得到关键结论:交换 \(p_i\)\(p_j\) 会使 \(P\) 的逆序对总数减少 \(2j-2i-1\),同时使 \(\sum\limits_{i=1}^{n} |p_i-i|\) 减少 \(2j-2i\)。而我们知道,当排列 \(P\) 升序时,这两个值应当都为 \(0\)。不妨记初始时 \(P\) 的逆序对总数为 \(a\)\(\sum\limits_{i=1}^{n} |p_i-i|=b\),则进行的操作次数为 \(b-a\)。接下来的问题转化为动态维护 \(a\)\(b\)

\(b\) 的值可以在每次修改时直接更新,主要问题是维护 \(a\) 的值。

我们发现这就是排队。考虑分块做法,对于每一块维护一个 vector,对整块二分计算贡献,对散块暴力计算贡献。此时若块长为 \(B\),则单次修改复杂度为 \(O(\dfrac{n}{B} \log B + B)\)。平衡块长应该可以通过。

然后发现过不了。但是我们可以采用更高明的技巧,做到 \(O(n \sqrt{n})\) 的复杂度。

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int N=500000,B=721;
int block_id[N+10],block_l[B+10],block_r[B+10],block_cnt;
bool cnt_pos[B+10][N+10];
int cnt_block[B+10][B+10];
int n,p[N+10],fenwick[N];
int my_abs(int num){
	if(num>=0) return num;
	else return -num;
}
long long pre_dis,pre_inv;
void modify(int pos,int val,bool cmd){
	int dif;
	if(cmd==false){
		dif=-1;
	}
	else{
		dif=1;
	}
	for(int i=block_id[pos];i<=block_cnt;i++){
		cnt_pos[i][val]=cmd;
		cnt_block[i][block_id[val]]+=dif;
	}
}
int query(int pos,int val){
	int ans=0;
	if(block_id[pos]>1){
		for(int i=1;i<=block_id[val]-1;i++){
			ans+=cnt_block[block_id[pos]-1][i];
		}
		for(int i=block_l[block_id[val]];i<=val;i++){
			ans+=cnt_pos[block_id[pos]-1][i];
		}
	}
	for(int i=block_l[block_id[pos]];i<=pos;i++){
		if(p[i]<=val){
			ans++;
		}
	}
	return ans;
}
void init(){
	memset(block_l,0x3f,sizeof(block_l));
	memset(block_r,-0x3f,sizeof(block_r));
	for(int i=1;i<=N;i++){
		block_cnt=block_id[i]=(i-1)/B+1;
		block_l[block_id[i]]=min(block_l[block_id[i]],i);
		block_r[block_id[i]]=max(block_r[block_id[i]],i);
	}
}
int main(){
	init();
	int q;
	scanf("%d %d",&n,&q);
	for(int i=1;i<=n;i++){
		scanf("%d",&p[i]);
		pre_dis+=my_abs(p[i]-i);
		pre_inv+=i-1;
		if(i>1  &&  p[i]>1){
			pre_inv-=query(i-1,p[i]);
		}
		modify(i,p[i],1);
	}
	while(q--){
		int x,y;
		scanf("%d %d",&x,&y);
		int num_x=p[x],num_y=p[y];
		if(num_x>num_y){
			pre_inv--;
		}
		else{
			pre_inv++;
		}
		int cnt,minn=min(num_x,num_y),maxn=max(num_x,num_y);
		cnt=(query(y,maxn-1)-query(y,minn))-(query(x,maxn-1)-query(x,minn));
		if(num_x>num_y){
			pre_inv-=2*cnt;
		}
		else{
			pre_inv+=2*cnt; 
		}
		modify(x,num_x,0);
		modify(y,num_y,0);
		swap(p[x],p[y]);
		modify(x,num_y,1);
		modify(y,num_x,1);
		pre_dis-=my_abs(num_x-x)+my_abs(num_y-y);
		pre_dis+=my_abs(num_y-x)+my_abs(num_x-y);
		printf("%lld\n",pre_dis-pre_inv);
	}
	return 0;
}

过了。

甚至比根号老哥好写说是。

甚至比树套树好写说是。

分块最高!

posted @ 2025-12-16 15:57  Oken喵~  阅读(5)  评论(2)    收藏  举报