E. Anton and Permutation(分块)因为这个题入坑分块

博客围绕一段1~n的序列展开,有q次交换位置l和r元素的操作,需计算每次交换后序列的逆序对个数。题解指出逆序对个数变化与(l,r)区间和val[l]、val[r]大小关系有关,可通过分块查询区间比d小的元素个数,分块做法时间复杂度优且代码量少。

题目链接

题意:给你一段1~n的序列。q次操作,每次操作交换位置l,和位置r上的元素。问每次交换后当前序列的逆序对个数。

题解

  • 首先可以看出每次交换位置为l,r的数后,逆序对个数的变化只与(l,r)区间与val[l]和val[r]大小关系有关。
  • 设Si表示区间(l,r)比val[i]小的数,Bi表示区间(l,r)比val[i]大的数。
  • 未交换前,val[l]和val[r]在区间(l,r)产生的逆序对个数是  Sl + Br。
  • 交换后,val[l]和val[r]在区间(l,r)产生的逆序对个数是  Bl + Sr。
  • ans += (Bl + Sr)- (Sl + Br)
  • 设 len = r - l + 1;
  • ans += (Sr + len - Sl) - (Sl + len - Sr) = (Sr - Sl)*2
  • 当这些东西处理好后,就可以开始考虑用数据结构或者分块来查询区间比d小的元素个数了。也就是求Si。
  • 直接运用分块。对于完整的块使用二分,不完整的块直接暴力。
  • 对于元素交换。因为会用到动态数组,所以直接二分插入和删除元素,这样能始终保持每一个块都是有序的。

对于这个题来说,分块做法时间复杂度优于动态主席树,且代码量只有一半。

如果想学分块可以看一下这个:分块

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 2e5+100;
ll ans;
int n,m,blo,bl[N],val[N];
vector<int>ve[1000];
ll query(int l,int r,int d){
	if(l>r) return 0; 
	ll sum=0;
	for(int i=l;i<=min(r,bl[l]*blo);++i) if(val[i]<d) sum++;
	if(bl[l]!=bl[r])
		for(int i=(bl[r]-1)*blo+1;i<=r;++i) if(val[i]<d) sum++;
	for(int i=bl[l]+1;i<=bl[r]-1;++i)
	    sum+=upper_bound(ve[i].begin(),ve[i].end(),d)-ve[i].begin();//二分找到此块比d小的元素 
	return sum;
}
int main()
{
	cin>>n>>m;
	blo=sqrt(n);
	for(int i=1;i<=n;i++){
		bl[i]=(i-1)/blo+1;
		val[i]=i;
		ve[bl[i]].push_back(val[i]);
	}
	while(m--){
		int l,r;
		cin>>l>>r;
		if(l>r) swap(l,r);
		if(l==r) {//如果l==r对原序列无影响,直接输出答案 
			cout<<ans<<endl;
			continue;
		}
		ans+=(query(l+1,r-1,val[r])-query(l+1,r-1,val[l]))*2;
		if(val[l]<val[r]) ans+=1;else ans-=1;//这两个元素交换后对答案的贡献 
		if(bl[l]!=bl[r]){//将两个元素所在的块位置交换 
			int p=bl[l],q=bl[r];
			ve[p].erase(lower_bound(ve[p].begin(),ve[p].end(),val[l]));
			ve[p].insert(upper_bound(ve[p].begin(),ve[p].end(),val[r]),val[r]);
			ve[q].erase(lower_bound(ve[q].begin(),ve[q].end(),val[r]));
			ve[q].insert(upper_bound(ve[q].begin(),ve[q].end(),val[l]),val[l]);
		}
		swap(val[l],val[r]);
		cout<<ans<<endl;
	}
}

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值