分析一下树状数组求逆序对的原理
先离散
1.大牛们说的
2.我不会
3.暂时用不到 以后再补
判断a[i]前的某一项和a[i]是否构成逆序对
·
先贴上3个要用的函数 下面分析
1.lowbit函数
LL bit(LL x){
return x & -x;
}
2.不太好描述的函数
void add(LL x){
while (x < M){
c[x]++;
x += bit(x);
}
}
3.统计前i项中逆序对的个数的函数
LL getsum(LL x){
LL sum = 0;
while (x){
sum += c[x];
x -= bit(x);
}
return sum;
}
·
下面开始分析
当读入一个数据a[i]时候先调用add函数将大于a[i]的树状数组上的主线上的点全部加一(2^k为树状数组的主线)
表示a[i]要小于这个值 不会与之形成逆序对
用 i 减去树状数组中 c[a[i]] (上面说c[a[i]]表示不会构成逆序对的个数)表示的就是前i项中与a[i]构成逆序对的个数
·
举个例子
读入a[5] = 10
那么 c[10]++ c[16]++ c[32]++ ………………(结合树状数组的基本定理我们可以知道c[16]所表示的是小于16的数的个数)
我们现在读入 a[7] = 13
那么前7项中和a[7]不会构成逆序对的个数就是前7项中小于13的数的个数即为getsum(13)也就是getsum(a[7]);
·
·
统计逆序对的步骤
当我们读入第N个数时 我们可以统计出前N - 1个数中和第N个数组成逆序对的个数
对应的逆序对的个数是 i + 1 - getsum(a[i] + 1)
注意上面的两个加一是因为数组从0开始 如果从1开始读取就不需要
其中 i 是当前的个数
getsum(a[i])所得到的是前i项中比a[i]小的个数(即为不是逆序对的个数)
两者相减得对于a[i] 前 i 项中逆序对的个数
然后反向读取a[i]同理求出对于a[i]中 i 之后的逆序对的个数
两者相加得到的就是对于a[i]的总的逆序对的个数
结合例题分析
我们只需要统计出逆序对的个数 sum 这个数就是所需要交换的次数
Σ(1到sum)就是小朋友的不开心数
上代码
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>
#define LL long long
const int M = 1e6 + 5;
using namespace std;
LL c[M], sum[M], a[M], b[M];
LL bit(LL x){
return x & -x;
}
void add(LL x){
while (x < M){
c[x]++;
x += bit(x);
}
}
LL getsum(LL x){
LL sum = 0;
while (x){
sum += c[x];
x -= bit(x);
}
return sum;
}
int main(){
LL n, i;
int T;
scanf_s("%d", &T);
while (T--){
cin >> n;
for (i = 1; i < M; ++i)//打表N的累加
sum[i] = sum[i - 1] + i;
for (i = 0; i < n; ++i)
{ //当前点跟左边点形成的逆序对数
cin >> a[i]; //确保每个点都被更新
add(a[i] + 1);
b[i] = i + 1 - getsum(a[i] + 1);//b[i]所保存的是第i个数与左边的数构成的逆序对的个数
} // i+ 1 表示i的位置 getsum(a[i] + 1)表示的是前i项中不与a[i]构成逆序对的个数
memset(c, 0, sizeof(c));
LL res = 0;
{ //当前点跟右边点形成的逆序对数
add(a[i] + 1); // 再次计算c[] 计算出的是a[i]右边逆序对的个数
res += sum[b[i] + getsum(a[i])];//左边+右边表示总的逆序对的个数 在之前打好的表中找到对应的数;
}
printf("%lld\n", res);
}
return 0;
}