洛谷P1908 逆序对 [树状数组解法]
首先明白一个概念叫做离散化
在上面介绍的树状数组中,只需要开一个与原序列中最大元素相等的长度数组就行,那么如果我的序列是,本来个元素,却需要开到这么大,造成了巨大的空间浪费。
离散化就是另开一个数组, 用来存放第大的数在原序列的什么位置,比如原序列,第一大就是,他在中的位是,所以,同理,········所以数组为,转换之后,空间复杂度就没这么高了,但不是求中的逆序对了,而是求中的正序对,来看一下怎么求的:
首先把放到树状数组中,此时只有一个数,中比小的数没有,sum+=0
再把放到树状数组中,此时只有两个数,比小的数只有一个,sum+=1
把放到树状数组中,此时只有三个数,比小的数只有一个,sum+=1
把放到树状数组中,此时只有四个数,比小的数有三个,sum+=3
把放到树状数组中,此时只有五个数,比小的数有四个,sum+=4
最后算出来,总共有个逆序对,可以手算一下原序列,也是个逆序对,
具体实现:
-
令
-
根据原数组中的元素的大小进行排序
二、黄海修改后的代码
#include <bits/stdc++.h>
using namespace std;
const int N = 500010;
typedef long long LL;
#define lowbit(x) (x & -x)
LL ans;
int n;
//每个输入的数字,我们关心两个方面:1、数值 2、位置(序号)
struct Node {
int val;
int id;
} d[N];
bool cmp(Node a, Node b) {
if (a.val == b.val) return a.id > b.id; //两个数一样大,序号大的靠前,这样统计出来的正序对才正确
return a.val > b.val; //数不一样大,数大的靠前
}
//下面是树状数组模板
int t[N];
//将序列中第x个数加上k
void add(int x, int k) {
for (int i = x; i <= n; i += lowbit(i)) t[i] += k;
}
//查询序列前x个数的和
int sum(int x) {
int sum = 0;
for (int i = x; i; i -= lowbit(i)) sum += t[i];
return sum;
}
int main() {
scanf("%d", &n);
//读入每个数,分别将数值、序号记录到结构体数组d中
for (int i = 1; i <= n; i++) {
scanf("%d", &d[i].val);
d[i].id = i;
}
//对结构体数组进行排序,大的在前,小的在后;如果数值一样,序号大的在前,小的在后
sort(d + 1, d + 1 + n, cmp);
//逆序对的定义:i<j && d[i]>d[j]
// d数组是由大到小排完序的,按由大到小的顺序动态维护树状数组,计算每次变化后出现的i<j 的个数。
for (int i = 1; i <= n; i++) {
//将d[i].id放入树状数组,描述这个号的数字增加了1个
add(d[i].id, 1);
//查询并累加所有比当前节点id小的数字个数
ans += sum(d[i].id - 1);
}
//输出结果
cout << ans << endl;
return 0;
}
三、原版本代码
#include <bits/stdc++.h>
using namespace std;
const int N = 500010;
typedef long long LL;
LL ans;
//原数组/ 离散化后的数组/ 树状数组
int a[N], d[N], t[N];
int n;
bool cmp(int x, int y) {
if (a[x] == a[y]) return x > y; //避免元素相同
return a[x] > a[y]; //按照原序列第几大排列
}
//返回非负整数x在二进制表示下最低位1及其后面的0构成的数值
int lowbit(int x) {
return x & -x;
}
//将序列中第x个数加上k
void add(int x, int k) {
for (int i = x; i <= n; i += lowbit(i)) t[i] += k;
}
//查询序列前x个数的和
int sum(int x) {
int sum = 0;
for (int i = x; i; i -= lowbit(i)) sum += t[i];
return sum;
}
int main() {
scanf("%d", &n);
//离散化,初始化d数组的值为索引号
for (int i = 1; i <= n; i++) scanf("%d", &a[i]), d[i] = i;
//对d数组进行排序,排序的依据是a[index]的大小,大的在前,小的在后,如果一样的话,index大的在前
sort(d + 1, d + n + 1, cmp);
//如此操作后,d数组记录的是离散化后,第i大的数出现在原来的哪个位置上
//比如 d[1]=5 ,描述着第1大的数字,原来是第5号的位置
//遍历d数组,找出正序对,对应着原数组的逆序对
for (int i = 1; i <= n; i++) {
add(d[i], 1);
ans += sum(d[i] - 1);
}
cout << ans;
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!
2020-05-04 Windows下使用SSH密钥实现免密登陆Linux服务器
2013-05-04 我们项目中需要准备的技术
2013-05-04 window 2003 实现多用户远程登录
2013-05-04 XSS的知识普及和预防办法
2013-05-04 免积分下载CSDN软件和新浪资料