蓝桥杯-小朋友排队-python

题目描述: 

n 个小朋友站成一排。现在要把他们按身高从低到高的顺序排列,但是每次只能交换位置相邻的两个小朋友。每个小朋友都有一个不高兴的程度。开始的时候,所有小朋友的不高兴程度都是0。如果某个小朋友第一次被要求交换,则他的不高兴程度增加1,如果第二次要求他交换,则他的不高兴程度增加2(即不高兴程度为3),依次类推。当要求某个小朋友第k次交换时,他的不高兴程度增加k。

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

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

【数据格式】
    输入的第一行包含一个整数n,表示小朋友的个数。
    第二行包含 n 个整数 H1 H2 … Hn,分别表示每个小朋友的身高。
    输出一行,包含一个整数,表示小朋友的不高兴程度和的最小值。

例如,输入:

3

3 2 1
程序应该输出:
9

【样例说明】
   首先交换身高为3和2的小朋友,再交换身高为3和1的小朋友,再交换身高为2和1的小朋友,每个小朋友的不高兴程度都是3,总和为9。

【数据规模与约定】
    对于10%的数据, 1<=n<=10;
    对于30%的数据, 1<=n<=1000;
    对于50%的数据, 1<=n<=10000;
    对于100%的数据,1<=n<=100000,0<=Hi<=1000000。

资源约定:
峰值内存消耗 < 256M
CPU消耗  < 1000ms

思路

这里大佬的思路可以借鉴大佬思路

大佬是用c语言实现的,我们这里使用python实现。

根据题意,通过调整最终要得到一个从小到大的数组,其中一个小朋友被调整的次数为n,他的不高兴的程度为n*(n+1)/2,所以我们只要知道每一位小朋友被调换的次数再全部加起来,就得到了我们最终的值

我们该怎么计算每个小朋友被调换的次数呢?这里我们用到了一种数据结构,树状数组。通过使用树状数组,可以很快的算出前缀和。通过前缀和,我们可以算出每个小朋友左边有多少人比他高,右边有多少人比他矮,两者相加,就是这个小朋友的n了。

我们这里用树状数组做两次逆序对,一次是正序处理,从左往右,找到右边矮的,一次是倒序处理,从右往左,找出左边高的。

关于逆序对的介绍可以参考这篇博客树状数组求逆序对

正序处理:

当前已经处理的数字个数减掉当前数字的前缀和即为以该数为较小数的逆序对个数。如图,比如当前已经读了n个数,而前缀和只有n-2个数,说明当前读到的数比之前读到的两个数要小,则找到了两个逆序对

   k[i]=i-1-sum(H[i])

逆序处理:

用树状数组倒序处理数列,当前数字的前一个数的前缀和即为以该数为较大数的逆序对的个数。因为是从后往前读,当读到 i 时,发现它的前一个数的前缀和>0,则说明之前读过的数中有小于当前数的,我们将这个值加上前缀和即可

    k[i]+=sum(H[i]-1)

代码 

N=100000

def discretization(h):
    temp=list(set(h))
    temp.sort()
    for i in range(len(h)):
        h[i]=temp.index(h[i])+1

def lowbit(x):
    return x&-x

def update(x,d):
    while x<N:
        tree[x]+=d
        x+=lowbit(x)

def sum(x):
    s=0
    while x>0:
        s+=tree[x]
        x-=lowbit(x)
    return s

n=int(input())
Hold=list(map(int,input().split()))
H=[0 for _ in range(N)]
for i in range(0,n):
    H[i+1]=Hold[i]+1

k=[0 for _ in range(N)]

#discretization(H)
tree=[0 for _ in range(N)]
for i in range(1,n+1):
    k[i]=i-1-sum(H[i])
    update(H[i],1)

tree=[0 for _ in range(N)]
for i in range(n,0,-1):
    k[i]+=sum(H[i]-1)
    update(H[i],1)
res=0
for i in range(1,n+1,1):
    res+=int((1+k[i])*k[i]/2)
print(res)

这个代码在蓝桥杯官网会超时,但在报名课程中可以通过,应该是系统问题

感谢罗老师的教程。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

晓宜

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值