题目链接
题意:给你一段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;
}
}