原题:
思路:
其实就是关于逆序对的问题。每个小朋友的不高兴程度是以其交换次数为等差数列的和(an=n),而每个小朋友的交换次数则取决于(排在他前面还比他高的人)+(排在他后面还比他矮的人)。
方法:
最简单的用暴力遍历,但TLE时间受限。
这里可以考虑归并排序和树状数组,时间复杂度都是 O(nlogn)
题解:
1.归并排序+二分
思路参考:数据结构--归并排序的应用(求逆序数 蓝桥杯--小朋友排队) - ruo_yu - 博客园
def merge(a,l,r):
if l>=r: return
mid=(l+r)//2
tmp,i,j=[],l,mid+1
merge(a,l,mid)
merge(a,mid+1,r)
while i<=mid and j<=r:
if a[i]<=a[j]:
tmp.append(a[i])
i+=1
else:
tmp.append(a[j])
high[a[j]]+=mid-i+1 #从i到mid的每个数与j组成逆序对,求出小朋友的high值
j+=1
if i<=mid:
tmp.extend(a[i:mid+1])
elif j<=r:
tmp.extend(a[j:r+1])
a[l:r+1]=tmp[:]
def binary(a,k): #二分查找数组中与k相等的左边界下标
l,r=0,len(a)
while l<r:
mid=(l+r)//2
if a[mid]<k:
l=mid+1
elif a[mid]>=k:
r=mid
return l
n=int(input())
h=[-1]+list(map(int,input().split()))
pos,low,high=[0]*(n+1),[0]*(n+1),[0]*(n+1)
#pos[k]:第k高小朋友在队中的位置
#low[k]:第k高小朋友后面比它矮的小朋友数量
#high[k]:第k高小朋友前面比它高的小朋友数量
a=h[:]
a.sort()
for i in range(1,n+1): #对h[]重新编号
k=binary(a,h[i])
h[i]=k #身高从低到高重新编号
pos[h[i]]=i #第k矮小朋友排在原队列的第i个
a[k]-=1 #避免重复查找,再次查到a[k]的时候查到的是后一个
merge(h,1,n)
ans=0
for i in range(1,n+1):
k=pos[i] #第i矮的小朋友编号
w=i #w表示第i矮的
low[i]=w-1-(k-1-high[i]) #w-1表示比k低的总人数,k-1-high表示排在k前面比k低的人数
t=high[i]+low[i]
ans+=t*(t+1)//2
print(ans)
2.树状数组
注意树状数组中记录的是每个数出现的次数
n=int(input())
N=1000010
h=[-1]+[int(i)+1 for i in input().split()]
c=[0]*N #树状数组
cnt=[0]*N #记录每个小朋友的交换次数
m=max(h)
def lowbit(n):
return n&(-n)
def update(x):
i=x
while i<=m:
c[i]+=1
i+=lowbit(i)
def getsum(x):
sum,i=0,x
while i>=1:
sum+=c[i]
i-=lowbit(i)
return sum
#求左边比他大的数
for i in range(1,n+1):
update(h[i])
cnt[i]+=i-getsum(h[i]) #i是总人数,getsum(h[i])是小于等于h[i]的人数
#求右边比他小的数
c=[0]*N
for i in range(n,0,-1):
update(h[i])
cnt[i]+=getsum(h[i]-1)
ans=0
for i in range(1,n+1):
ans+=cnt[i]*(cnt[i]+1)//2
print(ans)
使用树状数组时格外注意下标从1开始,而本题小朋友的身高有可能是0,所以把身高整体+1,既不会影响逆序对的个数,也防止lowbit(0)=0而出现死循环的情况。
提交
由于当时未设立Python组,所以1s的时间可能有些超时,大家可以在C语言网评测一下。
C语言网 - 编程入门学习 - 实用的编程在线学习网站 (dotcpp.com)