sag求——逆 序 对
Posted zaza-zt
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了sag求——逆 序 对相关的知识,希望对你有一定的参考价值。
求——逆 序 对
Part 0:作者前言(废话)
以前其实早就学过用二路归并排序的方法求序列的逆序对,因为一直没有学会二路归并,所以逆序对一直不会做
前几天学了线段树,然后无意间在书上看到了“线段树求逆序对”这样的问题……
于是果断魔改一发线段树求一手逆序对。。。然后就有了这个博客
另外,祝贺我考试通过了,暂时不会AFO啦!!!
Part 1:逆序对是什么?
给出如下定义:
对于一个给定的序列a,若序列中任意两个元素组成的二元组<a[i],a[j]>满足:a[i]>a[j],且i<j,则称这个二元组是序列a的一个逆序对
Part 2:魔改后线段树求逆序对的思路?
显然,我们知道用线段树很容易就可以维护区间和
所以第一步,我们在序列a的值域上建一个线段树,维护区间和,代表序列在任意一个区间中包含的元素数量
第二步,扫描序列a中的每一个元素,求出比a大的元素有几个(转换为线段树的语言就是询问a[i]到n的区间和)
第三步,刚才第二步已经求出数列中包含第a[i]个数的逆序对总数了,累计答案即可
(没错就是这么简单)
Part 3:两极反转!!!
然而……你以为这么简单就结束了???
NONONO!!!!
这个做法最最最最大的弊端就是:建树!
我们是在序列a的值域上建立线段树,于是……就有了下面这种情况
(毒瘤)出题人给出序列a的每个元素均在长整型范围内,那么这个做法还没开始,就结束了。。。
那么,问题来了——How to deal with this f**king situation?
离散化(李散花)大法好啊!!!
我们注意到:我们只是利用到了序列a中元素的大小关系,所以没有必要存下a序列中的每个数是多少
举个生动形象的栗子:
给出两个序列:a,b
a[4]={0x7f7f7f7f,1,2,3}
b[4]={4,1,2,3}
虽然4和0x7f7f7f7f差了好多,但是不影响这两个数列的逆序对数(都是3对)
那么我们的需求变为:把序列a中任意元素的值映射为大小不超过a的元素个数的另一个值,并且保持逆序对数不变
实现方法就是排序+去重后把原值映射为他的下标,就可以做到上述要求
排序和去重,当然了伟大的#include<algorithm>库里早就给我们打造好了这两个函数:
sort()和unique(),顺带一提sort()函数时间复杂度稳定为O(nlogn),他并不是简单的快排,sort源码很复杂,这里不多做解释(感谢zay学长告诉我QwQ)
言归正传,我们离散化之后,按照上面的步骤来就可以啦!
Part 4:求逆序对源代码实现(加注释)
1 #include<algorithm> 2 #include<cstdio> 3 using namespace std; 4 typedef long long int ll;//十年OI一场空,不开long long见祖宗 5 const int maxn=500005; 6 int t[maxn],a[maxn],n; 7 ll ans; 8 void discretization(){//离散化 9 scanf("%d",&n);//n是数据总量 10 for(int i=1;i<=n;i++){ 11 scanf("%d",a+i);//输入元素的同时copy一份,用来排序 12 t[i]=a[i]; 13 } 14 sort(t+1,t+n+1);//从小到大快排 15 int m=unique(t+1,t+n+1)-t-1;//去重,unique返回去重后数组长度(这个说法极其不准确,只是便于理解,如果您想了解更多,百度搜索C++ unique) 16 for(int i=1;i<=n;i++) 17 a[i]=lower_bound(t+1,t+m+1,a[i])-t;//寻找a[i]的下标并且用下标覆盖掉原来的a[i] 18 } 19 struct sag{//普通的自带大常数线段树(因为出题人没有卡常习惯QwQ) 20 int l,r;//如果普通线段树GG了的话,可以考虑zkw线段树优化 21 ll v; 22 sag *ls,*rs; 23 inline void push_up() { v=ls->v+rs->v; }//维护区间和 24 inline bool in_range(const int L,const int R) { return (L<=l)&&(r<=R); } 25 inline bool outof_range(const int L,const int R) { return (r<L)||(R<l); } 26 void update(const int L,const int R){ 27 if(in_range(L,R)) v++;//找到这个叶子结点,线段树中的这个元素数量+1 28 else if(!outof_range(L,R)){ 29 ls->update(L,R); 30 rs->update(L,R); 31 push_up();//从下往上更新逆序对数 32 } 33 } 34 ll query(const int L,const int R){ 35 if(in_range(L,R)) return v; 36 if(outof_range(L,R)) return 0; 37 return ls->query(L,R)+rs->query(L,R);//返回区间和 38 } 39 }*rot; 40 sag byte[maxn<<1],*pool=byte;//内存池建树 41 sag* New(const int L,const int R){ 42 sag *u=pool++; 43 u->l=L,u->r=R; 44 if(L==R){ 45 u->v=0; 46 u->ls=u->rs=NULL; 47 }else{ 48 int Mid=(L+R)>>1; 49 u->ls=New(L,Mid); 50 u->rs=New(Mid+1,R); 51 u->push_up(); 52 } 53 return u; 54 } 55 int main(){ 56 discretization(); 57 rot=New(1,n);//建立1-n的线段树 58 for(int i=1;i<=n;i++){//枚举每个元素 59 ans+=rot->query(a[i]+1,n);//因为相等元素不构成逆序对,所以a[i]+1 60 rot->update(a[i],a[i]);//该元素数量++ 61 } 62 printf("%lld",ans); 63 return 0; 64 }
以上是关于sag求——逆 序 对的主要内容,如果未能解决你的问题,请参考以下文章
2021-10-17:逆波兰表达式求值。根据 逆波兰表示法,求表达式的值。有效的算符包括 +-*/ 。每个运算对象可以是整数,也可以是另一个逆波兰表达式。说明:整数除法只保留整数部分。给定逆波兰(代码