逆序对问题也是困扰博主有过一段时间,所以写一篇博客记录一下
解决动态逆序对首先要会普通逆序对
#include<bits/stdc++.h>
using namespace std;
const int N = 5e5+15;
typedef long long ll;
#define int long long
int c[N],a[N];
int n;
//顺序枚举保证了i<j getsum()保证logn求出a[1]+..a[j]
int lowbit(int x){return x&-x;}
ll getsum(int x){
ll res=0;
while(x){
res+=c[x];
x-=lowbit(x);
}
return res;
}
void update(int x){
while(x<=n){
c[x]+=1;
x+=lowbit(x);
}
}
signed main(){
ios::sync_with_stdio(false);cin.tie(0);
cin>>n;vector<int>tep;
for(int i=1;i<=n;i++){
cin>>a[i];tep.push_back(a[i]);
}
sort(tep.begin(),tep.end());
tep.erase(unique(tep.begin(),tep.end()),tep.end());
for(int i=1;i<=n;i++){
a[i]=lower_bound(tep.begin(),tep.end(),a[i])-tep.begin()+1;
}
ll ans=0;
// for(int i=1;i<=n;i++){
// update(a[i]);
// ans+=i-getsum(a[i]);//要理解:实际上是求 在当前序列之前 比当前数大的有几个 i是当前的总个数 树状数组只是加速了前缀和的过程
// }//i从1枚举到n保证每一位求的都是他之前的数 getsum()保证了(a[i]<a[j])【代表着比当前数小的有多少个,i-getsum()自然就是(序列号1--i)比当前数大的有几个】 i是当前的个数
// //因为对于这个式子要这么看i<j--pi>pj[不要看被两个变量搞混了,实际上是个二层循环,不妨固定j那么正常做法就是j外层循环i内层循环]
// //对于每个j (1---j-1)有多少个pi>pj
for(int i=n;i>0;i--){
ans+=getsum(a[i]-1);
update(a[i]);
}//所以按照这个思维来想 这样做也是可以的
cout<<ans<<endl;
}
代码里写的很清楚,解决普通逆序对有两种解法;
问题的核心在于如何计算逆序对
假如有一个数列
4 5 1 3 2
从贡献的角度我们可以显然得知 【某一个数对逆序数的贡献 = 他左边比他大的数的个数 + 他右边比他小的数的个数】
但你会发现:一个数会被重复计算两次
所以只要算一侧就行了——也就是本题的结论
要不就累加一个数左侧比他大的,或者累加一个数右侧比他小的
比他大的 就是 getsum(x)(这不就统计了0~x的个数嘛)【这也就是为什么要离散化,不离散化开不下】
当前总个数是 i 所以结果是 i - getsum(x)
__每次都要快速修改&&求和所以用BIT这题就做完啦
如果你不会树套树,可以先去做一下*动态第K小*P2617 Dynamic Rankings
PS:实际上不是BIT套主席树 而是 BIT套动态开点权值线段树
会了之后来看P3157 动态逆序对
这题还是一样的思路,先算出总逆序对个数,然后在每一次修改的时候用贡献的思想想出删除一个数产生的影响(贡献是多少)(前文有提)
由于主席树是静态结构,但无法避免要修改和查询,所以用BIT维护一下
总的时间复杂度和空间复杂度都是nloglog
#include<bits/stdc++.h>
using namespace std;
const int N = 1e5+5;
typedef long long ll;
int n,m;
int tr[N],lc[N<<7],rc[N<<7];
ll sm[N<<7];
int pos[N];
int cnt;
void insert(int &now,int l,int r,int k){
if(!now)
now=++cnt;
sm[now]++;
if(l==r)
return;
int mid=l+r>>1;
if(k<=mid)
insert(lc[now],l,mid,k);
else
insert(rc[now],mid+1,r,k);
}
ll query(int rt,int l,int r,int x,int y){
if(x<=l&&r<=y)
return sm[rt];
ll ans=0;
int mid=l+r>>1;
if(x<=mid)
ans+=query(lc[rt],l,mid,x,y);
if(y>mid)
ans+=query(rc[rt],mid+1,r,x,y);
return ans;
}
void update(int now,int l,int r,int k){
if(!now)
return;
sm[now]--;
if(l==r)
return;
int mid=l+r>>1;
if(k<=mid)
update(lc[now],l,mid,k);
else
update(rc[now],mid+1,r,k);
}
int lowbit(int x){return x&(-x);}
void slv(){
cin>>n>>m;ll ans=0;
for(int i=1;i<=n;i++){
int x;cin>>x;pos[x]=i;
for(int j=i;j<=n;j+=lowbit(j))
insert(tr[j],1,n,x);
if(x!=n){
for(int j=i;j>0;j-=lowbit(j)){
ans+=query(tr[j],1,n,x+1,n);
}
}
}
while(m--){
int x;cin>>x;
cout<<ans<<'\n';
if(x!=n){
for(int i=pos[x]-1;i>0;i-=lowbit(i)){
ans-=query(tr[i],1,n,x+1,n);
}
}
if(x!=1){
//一开始的错误写法:BIT只能维护快速前缀和 不能维护后缀啊 但我想统计后面的咋办 总-前呗
// for(int i=pos[x]+1;i<=n;i+=lowbit(i)){
// ans-=query(tr[i],1,n,1,x-1);
// }
for(int i=n;i>=1;i-=lowbit(i))
ans-=query(tr[i],1,n,1,x-1);
for(int i=pos[x];i>=1;i-=lowbit(i))
ans+=query(tr[i],1,n,1,x-1);
}
for(int i=pos[x];i<=n;i+=lowbit(i)){
update(tr[i],1,n,x);
}
}
}
int main(){
ios::sync_with_stdio(false);cin.tie(0);
slv();
}
友情提示:要开LL哦 还有主席树得开大
赠送一道 动态区间逆序对 E. Anton and Permutation