[NOI Online 提高组]冒泡排序(树状数组)
题面
略
分析
记(f_i=sum_{j=0}^i[a_j>a_i])表示([1,i-1])中比(a_i)大的数的个数。那么逆序对数就是(sum_{i=1}^n f_i).可以用树状数组预处理得出。
手玩一下冒泡排序过程发现,每冒泡一轮,所有的(f_i)会-1(减到0为止),然后会向前移动一位,即(f_{i-1} leftarrow f_i-1).
那么冒泡(k)轮之后,(f_1,f_2,dots f_k)会消失,所有(leq k)的(f_i)会变为0。 注意到对于(i in [1,k]),(f_i < k).因此这些数都会减为0,那么我们就不需要考虑平移,只需要考虑统计大于(k)的数,也就是说,(f_i)的位置没有意义.(比赛时没想到这一点,硬上一个树套树被卡成暴力分)
那么答案就是:
[sum_{i=1}^n f_i[f_i>k]-ksum_{i=1}^n[f_i>k]
]
因此我们要统计出(>k)的(f_i)的值之和,以及它们的个数。可以用两个树状数组实现。由于(f_i<n),我们可以把每个(f_i)的值存储在树状数组的第(f_i)位,查询时区间求和即可。
最后考虑修改。修改的时候只会改变(f_x)和(f_{x+1})的值,若(a_x>a_{x+1}),则交换后比(a_{x+1})小的数少了一个,那么(f_{x+1})-1.反之(f_x)+1,然后交换(f_x,f_{x+1}). 可以用树状数组单点修改实现
代码
#include<iostream>
#include<cstdio>
#include<utility>
#include<cstring>
#define maxn 200000
using namespace std;
typedef long long ll;
int n,m,a[maxn+5],f[maxn+5];
struct fenwick_tree{
ll c[maxn+5];
inline int lowbit(int x){
return x&-x;
}
void ini(){
for(int i=1;i<=n;i++) c[i]=0;
}
void add(int x,int v){
if(x==0) return;
for(int i=x;i<=n;i+=lowbit(i)) c[i]+=v;
}
ll sum(int x){
ll ans=0;
for(int i=x;i>0;i-=lowbit(i)) ans+=c[i];
return ans;
}
ll query(int l,int r){
return sum(r)-sum(l-1);
}
}T1,T2,T3;
void insert_f(int val){
T2.add(val,1);
T3.add(val,val);
}
void del_f(int val){
T2.add(val,-1);
T3.add(val,-val);
}
pair<ll,ll>query_f(int l,int r){
return make_pair(T2.query(l,r),T3.query(l,r));
}
void change(int x){
del_f(f[x]);
del_f(f[x+1]);//先把原来的去掉
if(a[x]<a[x+1]) f[x]++;
else f[x+1]--;
swap(f[x],f[x+1]);
swap(a[x],a[x+1]);
insert_f(f[x]);
insert_f(f[x+1]);
}
ll query(int k){
if(k>=n) return 0;
pair<ll,ll>tmp=query_f(k+1,n);
//对于位置在k之前的,逆序对数一定<k,所以会被减成0
//我们只需要算所有值>k的即可,不需要考虑位置
return tmp.second-tmp.first*k;
}
int main(){
int cmd,x;
scanf("%d %d",&n,&m);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
for(int i=1;i<=n;i++){
f[i]=i-1-T1.sum(a[i]);
T1.add(a[i],1);
}
for(int i=1;i<=n;i++) insert_f(f[i]);
for(int i=1;i<=m;i++){
scanf("%d %d",&cmd,&x);
if(cmd==1) change(x);
else printf("%lld
",query(x));
}
}