【树状数组 求逆序对】排序

首先需要了解逆序对是什么

逆序对就是如果i > j && a[i] < a[j],这两个就算一对逆序对。其实也就是对于每个数而言,找找排在其前面有多少个比自己大的数

那么思路就来了,树状数组又一次地优化了这种“需要遍历”的情况。那不就很容易了吗?依次把序列里的数放到树状数组中的A[i]上去(实际是以C[i]形式的插入函数),注意A[i]是以数值大小从小到大排列的。先插入的说明排在序列的前面,那么后插入的就可以看看之前插入的比你大的数有多少,即i-sum(i),其实也就是看序列前面比你大的数有多少个,即找逆序对。

通过树状数组找逆序对的原理(图解,我看了就懂了):

https://blog.csdn.net/ssimple_y/article/details/53744096


了解技巧“离散化”

这个技巧很有局限性(至少以我目前的认知来说),几乎只适合在树状数组求逆序对来结合使用的。

什么时候要用这个技巧呢?根据以上说的原理,我们知道需要以“数值大小”作为A[i]标准来升序排,所以需要给C数组开元素最大可能值的内存。但万一输入的数据很大,那么C数组岂不是要开很大?内存超限!!而如果要用离散化,只需要给C数组开元素数量的内存。

建立一个结构体包含val和id, val就是输入的数,id表示输入的顺序。然后按照val从小到大排序,如果val相等,那么就按照id排序。

如果没有逆序的话,肯定id是跟i(表示拍好后的顺序)一直一样的,如果有逆序数,那么有的i和id是不一样的。所以,利用树状数组的特性,我们可以简单的算出逆序数的个数。

如果还是不明白的话举个例子。(输入4个数)

输入:9 -1 18 5

输出 3.

输入之后对应的结构体就会变成这样

val:9 -1 18 5

id:  1  2  3  4

排好序之后就变成了

val :  -1 5 9 18

id:      2 4  1  3

2 4 1 3 的逆序数 也是3

之后再利用树状数组的特性就可以解决问题了

我觉得很神奇,直接得到“离散化”的结论把原序列中每个元素的值和下标存到一个结构体node里去,之后把node数组按元素值大小从小到大排序(注意结构体里的重载<运算符的写法 不是男左女右了我结论有误T T 反正考场上试一试即可),这样得到的结点的下标值即是离散化结果,等效于原序列的数值。把这些下标值当成原序列,按照树状数组求逆序对的原理做。



习题:排序



大致思路


这个就是逆序对的定义呀!求逆序对的模板题。

这道题如果不离散化,C开1e9内存,超限,所以离散化,C只需要开5e5内存。


AC代码

#include<iostream>
#include<bits/stdc++.h>
using namespace std;
const int maxn=500001;
int c[maxn];
struct Node
{
	int v,index;
	bool operator < (const Node &b) const
	{
		return v<b.v; //从小到大排序 
	}
}node[maxn];
int n;
void add(int i)
{
	while(i<=n)
	{
		c[i]++;
		i+=i&(-i);	
	}
}
long long sum(int i)
{
	long long res=0;
	while(i>0)
	{
		res+=c[i];
		i-=i&(-i);
	}
	return res;
}

int main()
{
	cin>>n;
	int a;
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&a);
		node[i].index=i;
		node[i].v=a;
	}
	sort(node+1,node+1+n);
	long long ans=0;
	for(int i=1;i<=n;i++)
	{
		add(node[i].index);  //离散化结果—— 下标等效于数值
		ans+=i-sum(node[i].index); //得到之前有多少个比你大的数(逆序对)
	}
	cout<<ans;
	return 0;
}

  • 24
    点赞
  • 68
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
看大小就知道很全啦 查看地址 https://blog.csdn.net/qq_43333395/article/details/98508424 目录: 数据结构: 1.RMQ (区间最值,区间出现最大次数,区间gcd) 2.二维RMQ区间最大值 (二维区间极值) 3.线段树模板(模板为区间加法) (线段树染色) (区间最小值) 4.线性基 (异或第k大) 5.主席树(静态区间第k小) (区间中小于k的数量和小于k的总和) (区间中第一个大于或等于k的值) 6.权值线段树 (逆序对) 7.动态主席树 (主席树+树状数组) (区间第k大带修改) 8.树上启发式合并 (查询子树的优化) 9,树状数组模板 (区间异或和,逆序对) 扩展 10.区间不重复数字的和 (树状数组) 11.k维空间中离所给点最近的m个点,并按顺序输出(KD树) 12.LCA (两个节点的公共父节点) 动态规划: 1.LIS (最长上升子序列) 2.有依赖的背包 (附属关系) 3.最长公共子序列(LCS) 4.树形DP 5.状压DP-斯坦纳树 6.背包 7.dp[i]=min(dp[i+1]…dp[i+k]),multset 博弈: 1.NIM博弈 (n堆每次最少取一个) 2.威佐夫博弈(两堆每次取至少一个或一起取一样的) 3.约瑟夫环 4.斐波那契博弈 (取的数依赖于对手刚才取的数) 5.sg函数 数论: 1.数论 素数检验:普通素数判别 线性筛 二次筛法素数 米勒拉宾素数检验 2.拉格朗日乘子法(有等式约束条件的极值) 3.裂项(多项式分子分母拆分) 4.扩展欧几里得 (ax+by=c) 5.勾股数 (直角三角形三边长) 6.斯特林公式 (n越大越准确,n!) 7.牛顿迭代法 (一元多次方程一个解) 8.同余定理 (a≡b(mod m)) 9.线性所有逆元的方法 (1~p modp的逆元) 10.中国剩余定理(n个同余方程x≡a1(modp1)) 11.二次剩余((ax+k)2≡n(modp)(ax+k)^2≡n(mod p)(ax+k) 2 ≡n(modp)) 12.十进制矩阵快速幂(n很大很大的时候) 13.欧拉函数 14.费马小定理 15.二阶常系数递推关系解方法 (a_n=p*a_{n-1}+q*a_{n-2}) 16.高斯消元 17.矩阵快速幂 18.分解质因数 19.线性递推式BM(杜教) 20.线性一次方程组解的情况 21.解行列式的逆矩阵,伴随矩阵,矩阵不全随机数不全 组合数学: 1.循环排列 (与环有关的排列组合) 计算几何: 1.三角形 (面积)) 2.多边形 3.三点圆心和半径 4.扫描线 (矩形覆盖面积) (矩形覆盖周长) 5.凸包 (平面上最远点对) 6.凸多边形的直径 7.凸多边形的宽度 8.凸多边形的最小面积外接矩形 9.半平面交 图论: 基础:前向星 1.最短路(优先队列dijkstra) 2.判断环(tarjan算法) 3.最小生成树(Kruskal 模板) 4.最小生成树(Prim) 5.Dicnic最大流(最小割) 6.无向图最小环(floyd) 7.floyd算法的动态规划(通过部分指定边的最短路) 8.图中找出两点间的最长距离 9.最短路 (spfa) 10.第k短路 (spfa+A*) 11.回文树模板 12.拓扑排序 (模板) 13.次小生成树 14.最小树形图(有向最小生成树) 15.并查集 (普通并查集,带权并查集,) 16.两个节点的最近公共祖先 (LCA) 17.限制顶点度数的MST(k度限制生成树) 18.多源最短路(spfa,floyd) 19.最短路 (输出字典序最小) 20.最长路 图论题目简述 字符串: 1.字典树(多个字符串的前缀) 2.KMP(关键字搜索) 3.EXKMP(找到S中所有P的匹配) 4.马拉车(最长回文串) 5.寻找两个字符串的最长前后缀(KMP) 6.hash(进制hash,无错hash,多重hash,双hash) 7.后缀数组 (按字典序排字符串后缀) 8.前缀循环节(KMP的fail函数) 9.AC自动机 (n个kmp) 10.后缀自动机 小技巧: 1.关于int,double强转为string 2.输入输出挂 3.低精度加减乘除 4.一些组合数学公式 5.二维坐标的离散化 6.消除向下取整的方法 7.一些常用的数据结构 (STL) 8.Devc++的使用技巧 9.封装好的一维离散化 10.Ubuntu对拍程序 11.常数 12.Codeblocks使用技巧 13.java大数 叮嘱 共173页

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值