码迷,mamicode.com
首页 > 其他好文 > 详细

sag求——逆 序 对

时间:2020-07-05 17:33:56      阅读:68      评论:0      收藏:0      [点我收藏+]

标签:作者   pool   时间   更新   print   另一个   lin   时间复杂度   映射   

求——逆 序 对

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求——逆 序 对

标签:作者   pool   时间   更新   print   另一个   lin   时间复杂度   映射   

原文地址:https://www.cnblogs.com/zaza-zt/p/13230601.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!