数据结构-第十期——树状数组 - 逆序对与离散化
创始人
2024-06-03 02:45:14
0

例题:逆序对问题 

【题目描述】给定一个序列A_i,...,A_n。若i;,。请你写一个程序,在尽量短的时间内统计出"逆序对“的数目。
【输入格式】第1行是整数n ( 1≤n<500000),接下来1行,n个整数。

【输出格式】一个整数,为逆序对的数目。
【输入样例】
6
5 4 2 6 3 1

【输出样例】

11

样例分析:

5后面有4个数比它小,所以5和这四个数是4个逆序对,4后面有三个比它小的数,2后面有一个比它小的数,以此类推,从左到右依次检查每个数右边比它本身小的数的数量,然后相加,4+3+1+2+1=11,就可以得到所有逆序对的数量。 

(1)暴力法

模拟:先检查第一个数a,把后面所有数跟它比较,如果发现有一个比a,小,就是一个逆序对;再检查第二个数,第三个数......;直到最后一个数。
【代码】:

n =int(input ())
a = list(map (int, input().split()))
res = 0
for i in range(n):for j in range(i+1, n):if a[j]

复杂度:O(n^2)。本题n最大为5*10^5,暴力法超时了

(2)归并排序

观察暴力法的执行过程,发现和交换排序很像。
能否用交换排序的升级版归并排序,来处理逆序对问题?

 归并排序:

  • (1)分解。把初始序列分成长度相同的左右两个子序列,然后把每个子序列再分成更小的两个子序列......,直到子序列只包含1个数。用递归实现。
  • (2)合并。归并2个有序的子序列

对n个数进行归并排序:

  • (1)需要logn趟归并;
  • (2)在每一趟归并中,有很多次合并操作,一共需要O(n)次比较。复杂度:O(nlogn)

观察归并排序的一次合并过程,发现能利用这个过程来记录逆序对。

  • (1)在子序列内部,元素都是有序的,不存在逆序对;

              逆序对只存在于不同的子序列之间

  • (2)合并两个子序列时:

              如果前一个子序列的元素比后面子序列的元素小,无逆序对
              如果前一个子序列的元素比后面子序列的元素大,有逆序对,且有mid-(i-1)个
 

【代码】:

def merge(L, mid, R):global resi = L;j = mid+1;t=0while(i <= mid and j <= R):# 左半边和右半边有一个到边界就结束if(a[i] > a[j]):b[t] = a[j]; t+=1;j+=1   # 如果前一个子序列的元素比后面子序列的元素大,有逆序对,且有mid-(i-1)个res = res + mid-i+1   #记录逆序对数量else: b[t] = a[i]; t+=1;i+=1   # 如果前一个子序列的元素比后面子序列的元素小,无逆序对。
#一个子序列中的数都处理完了,另一个还没有,把剩下的复制过来while i <= mid:  b[t]=a[i];t+=1; i+=1while j <= R:b[t]=a[j];t+=1 ;j+=1for i in range(t): a[L+i] = b[i]#把排好序的b[]复制回给a[]def merge_sort(L, R): # 分治if L

优点:复杂度O(nlogn) 

缺点:存在大量拷贝,增加了时间成本;代码不如用树状数组简单。 

(3)树状数组与逆序对

用树状数组求逆序对,是树状数组的巧妙应用,比其他方法都好。

用树状数组解逆序对:把数字看成树状数组的下标
例如序列{5,4,2,6,3,1},对应a[5]、a[4]、a[2]、a[6]、a[3]、a[1]。
每处理一个数字,树状数组的下标所对应的元素数值加一,统计前缀和,就是逆序对的数量。
①倒序
用树状数组倒序处理数列,当前数字的前一个数的前缀和即为以该数为较大数的逆序对的个数

例如{5,4,2,6,3,1},倒序处理数字:

  • 数字1。把a[1]加一,计算a1l前面的前缀和sum(0),逆序对数量ans = ans + sum(0)=0
  • 数字3。把a[3]加一,计算a[3]前面的前缀和sum(2),逆序对数量ans = ans + sum(2)=1;
  • 数字6。把a[6]加一,计算a[6]前面的前缀和sum(5),逆序对数量ans = ans + sum(5)=1+2=3;
  • 等等.....

复杂度:处理n个数字,每个数字有加一add()求前缀和sum()操作(都是logn),所以总复杂度为O(nlogn)

②正序(虽然正序比较难,但也可能需要用到,例如下面例题)
当前已经处理的数字个数减掉当前数字的前缀和即为以该数为较小数的逆序对个数

例如{5,4,2,6,3,1},正序处理数字:

  • 数字5。把a[5]加一,当前处理了1个数,ans = ans + ( 1-sum(5))=0;
  • 数字4。把a[4]加一,当前处理了2个数,ans = ans +(2-sum(4))=0+1= 1;·
  • 数字2。把a[2j加一,ans = ans + (3-sum(2))=1+2= 3;
  • 数字6。把a[6]加一,ans = ans + (4-sum(6))=3+0= 3;
  • 等等。。。。  

离散化

        上面的处理方法“把数字看成树状数组的下标”有个问题,如果数字比较大,例如数字等于10^9,那么树状数组的空间也要开到10^9= 1G,远远超过了题目限制的空间。
        用“离散化”这个小技巧能解决这个问题。 

  • 离散化:把原来的数字,用它们的相对大小来替换原来的数值,而它们的顺序仍然不变
  • 例:{1,20543,19,376,546007640},它们的相对大小是{1,4,2,3,5}
  • 有多少个数字,离散化后的每个数字的大小就是多大。
  • 在用树状数组求解逆序对的题目中,离散化几乎是必须的。
  • 本题需处理500000个数字,离散化之后树状数组大小只需500000。

离散化方法1:重复数字离散化后不一样

a = [1,20543,19,376,546007640,19]
print(a)
b = sorted(a)
print(b)
for i in range (len(b)):k = a.index(b[i])a[k] = i+1    #a[a.index(b[i])]= i+1
print(a) # [1,5,2,4, 6,3]

离散化方法2:重复数字离散化后也一样

def discretization(h):b = list (set (h))    # 用集合去重b. sort()for i in range (len(h)) :h[i] = b.index(h[i])+1
a = [1,20543,19,376,546007640,19]
print(a)
discretization(a)    # 列表可以在函数中完成修改且不需要return
print (a) # [1,4,2,3,5,2]

【代码】:

# 数组树状标准模板
def lowbit(x):return x & -x
def update(x, d):while x <= n:tree[x] += dx += lowbit(x)
def sum(x):ans = 0while(x > 0):ans += tree[x] x -= lowbit(x)return ansn = int(input())
a = [0]+list(map(int,input ().split()))#从a[1]开始
# 离散化
b = sorted(a)
for i in range(n+1) : a[a.index (b[i])] = i+1tree = [0]*(n+1)
res = 0
for i in range(len(a)-1, 0, -1):update(a[i], 1)res += sum(a[i]-1)
print (res)

例题:小朋友排队

题目描述

n个小朋友站成一排。现在要把他们按身高从低到高的顺序排列,但是每次只能交换位置相邻的两个小朋友

每个小朋友都有一个不高兴的程度。开始的时候,所有小朋友的不高兴程度都是 0。

如果某个小朋友第一次被要求交换,则他的不高兴程度增加 1,如果第二次要求他交换,则他的不高兴程度增加 2(即不高兴程度为 3),依次类推。当要求某个小朋友第 k 次交换时,他的不高兴程度增加 k。

请问,要让所有小朋友按从低到高排队,他们的不高兴程度之和最小是多少。

如果有两个小朋友身高一样,则他们谁站在谁前面是没有关系的。

输入描述

输入的第一行包含一个整数 n,表示小朋友的个数。

第二行包含 n 个整数 H1​H2​...Hn​,分别表示每个小朋友的身高。

其中,1\leqslant n\leqslant 10^5,0\leq H_i\leqslant 10^6

输出描述

输出一行,包含一个整数,表示小朋友的不高兴程度和的最小值

输入输出样例

输入

3
3 2 1

输出

9

思路: 

  • 每个小朋友要交换多少次﹖

每一个小朋友最少的交换次数等于他左边比他高的人数加上右边比他矮的人数

  • 逆序对问题,包括2个逆序对:

        (1)他左边比他高的人; 

        (2)右边比他矮的人。

  • 用树状数组做两次逆序对,一次正序处理,一次倒序处理

【代码】 

N = 100010
def discret(sl: list):      #树状数组离散化(离散化方法2:重复数字离散化后也一样)hl = sorted(set(sl))for i in range(len(sl)):sl[i] = hl.index(sl[i]) + 1tree = [0] * N
def lowbit(x):return x & (-x)
def tree_add(x, d):while x <= N:tree[x] += dx += lowbit(x)
def tree_sum(x):ans = 0while x > 0:ans += tree[x]x -= lowbit(x)return ansn = int(input())
a = list(map(int, input().split()))     
discret(a)                              #对n个小朋友的身高进行离散化(究竟是什么样的小朋友身高会是1e6)
a = [0] + a#每个人交换次数=前面比他高的人数+后面比他矮的人数
excnt = [0] * N                         #记录n个小朋友每人交换的次数,0不用
for i in range(1, n + 1):               #正向统计,可以计算出a[i]前面比a[i]高的人数tree_add(a[i], 1)excnt[i] = i - tree_sum(a[i])tree = [0] * N                          #重新初始化tree数组
for i in range(n, 0, -1):               #逆向统计,可以计算出a[i]后面比a[i]矮的人数tree_add(a[i], 1)excnt[i] += tree_sum(a[i] - 1)      #统计和res = 0
for i in range(1, n + 1):               #统计总的不高兴值res += excnt[i] * (1 + excnt[i]) // 2   #n次交换,不高兴值是1+2+3+...+n
print(res)

相关内容

热门资讯

文艺节目主持词 文艺节目主持词四篇  主持词要把握好吸引观众、导入主题、创设情境等环节以吸引观众。在一步步向前发展的...
幼儿园六一儿童节主持词 幼儿园六一儿童节主持词尊敬的各位来宾、各位朋友大家下午好!沐浴着和风丽日,我们即将迎来花团锦簇、芳香...
教师节表彰大会校长的致辞 教师节表彰大会校长的致辞范文(精选6篇)  在平平淡淡的日常中,要用到致辞的地方还是很多的,致辞讲求...
婚礼开场白主持词 婚礼开场白主持词  利用在中国拥有几千年文化的诗词能够有效提高主持词的感染力。随着社会一步步向前发展...
会主持人开场白台词 会主持人开场白台词2013年会主持人开场白台词    甲:新年的钟声即将敲响,时光的车轮又留下了一道...
领导主持词 领导主持词三篇  主持词已成为各种演出活动和集会中不可或缺的一部分。在现今人们越来越重视活动氛围的社...
升学宴致辞 升学宴致辞(精选15篇)  在现实生活或工作学习中,大家一定都接触过致辞吧,致辞具有“礼仪性”或“仪...
农村白事的主持词开场白 农村白事的主持词开场白(精选10篇)  在发展不断提速的社会中,越来越多的人会用到开场白,好的开场白...
生日主持词的开场白   生日主持词开场白(一)  各位同事和寿星们,各人晚顶好!在这天高气爽、丹桂飘喷鼻的夸姣季候,咱们...
旅游文化节主持词 旅游文化节主持词  主持词的写作需要将主题贯穿于所有节目之中。现今社会在不断向前发展,主持人参与的事...
主持人串词 主持人串词  一、串词的语言特征  (串词的语言,可以说是用尽了所有的修辞手法,我们不可能去全讲,因...
浪漫婚礼司仪主持词 浪漫婚礼司仪主持词  主持词是主持人在台上表演的灵魂之所在。在现在的社会生活中,很多场合都需要主持人...
公司迎春晚会的主持词 公司迎春晚会的主持词  主持词的写作需要将主题贯穿于所有节目之中。在当今不断发展的世界,主持人在活动...
少儿节目主持词 精选少儿节目主持词4篇  主持词已成为各种演出活动和集会中不可或缺的一部分。随着社会一步步向前发展,...
王家卫电影经典台词 王家卫电影经典台词(精选50句)  我们爱看王家卫的电影,不止爱他所创造的那个光影世界,更爱他电影中...
演唱会主持台词 演唱会主持台词  (甲)尊敬的各位领导,  (乙)各位来宾,  (甲)敬爱的老师,  (乙)亲爱的同...
《你的名字》经典台词 《你的名字》经典台词  你的名字,是谁的心事,还记得你的名字里面的经典台词吗?以下是小编为你精心整理...
教研活动主持词 教研活动主持词  主持人在台上表演的灵魂就表现在主持词中。在当下的中国社会,主持成为很多活动不可或缺...
艺术节主持词开场白 艺术节主持词开场白  什么是艺术节  艺术节是文艺工作者及艺术家、艺术爱好者之间学术交流与学习的重要...
老板在公司年会致辞 老板在公司年会致辞15篇  在平平淡淡的学习、工作、生活中,大家最不陌生的就是致辞了吧,致辞具有针对...