题目描述
小陶在举重队负责后勤工作。举重队的训练场中有一个区域一排码放了n片杠铃片,每天运动员们训练完之后会将杠铃片放回,之后小陶需要重新整理杠铃片的顺序,使它们由轻到重依次排好。由于杠铃片很重,小陶每次只能选两片相邻的杠铃片,交换它们的位置。现在小陶想知道,这一天他至少需要交换多少次才能整理完毕?
已知n<=200000,0<=杠铃片重量<=200000。
输入格式
第一行一个正整数n,表示有n片杠铃片;
第二行n个整数,表示运动员们放回后每片杠铃片依次的重量。
输出格式
输出一个整数,表示小陶至少交换的次数。
样例
【样例输入】
10
16808 75250 50074 143659 108931 11273 27545 50879 177924 37710
【样例输出】
20
数据范围与提示
对于30%的数据: n<=5000 对于60%的数据:
n<=30000 对于100%的数据:
n<=200000,0<=杠铃片重量<=200000
一些想法
这道题依旧用树状数组做,这道题其实就是求冒泡排序的交换次数,也就是求有多少对逆序对。
简单来说,就是找一个数前面有多少个数比他大,然后将个数全部加起来,就得到要交换的次数了。是一种前缀查询,所以用树状数组做最好。
先定义一个树状数组用于储存前缀和(小于当前重量元素的数量)。然后主函数部分:因为输入数有可能是 0,所以要整体偏移加 1(避免死循环),然后答案累加已处理的数的数量( i-1 )减小于当前重量的元素个数,也就是累加要交换的次数(已处理个数减小于当前重量的元素个数,剩下的就是在当前数前面但大于当前数的元素个数,也就是要交换的次数),这里要用到查询小于当前数的元素个数(查询函数)。然后将当前数放进树状数组里,为后续查询提供数据。这就要用到修改数组。而这两个自定义数组都要用 lowbit 数组来划分区间。
函数部分,有三个自定义函数:
lowbit 函数:用位运算找 x 二进制的最后一个 1 。树状数组的所有操作都基于 lowbit 实现区间划分。
查询函数:查询 比 x 小的元素个数(前缀和查询),然后只要 x 大于 0(没有超出范围),然后将 x 不断减小(减自己的lowbit),跳转到前一个区间,将个数增加。(简单来说就是分解,将当前区间不断分解成多个小区间)。
修改函数:将第 x 个数增加 y ,因为前面的点有变化,所以包含这个点的在范围内的所有数都要跟着变化,因为父节点要随着子节点的变化而变化,所以每次增加自己的 lowbit ,也就是不断往后一个区间,直到达到边界,然后每一个区间都增加指定值。
注意:统计交换次数的数组要开 long long!!!
因为测试数据会很大,如果不开 long long 会出现越界行为,导致程序崩溃或答案错误。
AC代码
cpp
#include<bits/stdc++.h>
using namespace std;
int n,c[200005],x;
long long sum=0;
const int N=200020;
int lowbit(int x){
return x&-x;
}
int zs(int x){
int cnt=0;
for(;x>0;x-=lowbit(x)){
cnt+=c[x];
}
return cnt;
}
void sm(int x,int y){
for(;x<N;x+=lowbit(x)){
c[x]+=y;
}
}
int main(){
cin>>n;
for(int i=1;i<=n;i++){
cin>>x;
x++;
sum+=(i-1)-zs(x);
sm(x,1);
}
cout<<sum;
return 0;
}