逆序对-树状数组or暴力-python

SORT 公司是一个专门提供排序服务的公司,该公司的宗旨是:“顺序是美丽的”。他们的工作是通过一系列移动,将某些物品按顺序摆好。他们的服务是通过工作量来计算的,即移动物品的次数。所以,在工作前必须先考察工作量,以便向客户提出收费数目。

用户并不需要知道精确的移动次数,实质上,大多数人都是凭感觉来认定这一列物品的混乱程度。根据 SORTSORT 公司的经验,人们一般是根据“逆序对”的数目多少来称呼这一序列的混乱程度。假设将序列中第I件物品的参数定义为 Ai​,那么排序就是将 A1​,⋯An​ 从小到大排序。

若 i<j 且 Ai​>Aj​,则 <i,j>就为一个“逆序对".

SORT 公司请你写一个程序,在尽量短的时间内统计出”逆序对“的数目

运行限制

  • 最大运行时间:1s
  • 最大运行内存: 128M

思路:

暴力解法:

根据定义,我们遍历数组,如果发现当前的数字后面存在比他大的数,则计数器++。代码复杂度o(n^2)

代码

c = 0
b = input()
a = list(map(int,input().split()))
for i in range(0,len(a) - 1):
  for j in range(i + 1,len(a)):
    if a[i] > a[j]:
      c += 1
print(c)

树状数组:

有关这道题的树状数组的解法可以先参考这位博主的文章,讲的很详细。大佬的思路

树状数组是一种用来求前缀和的时间复杂度比较低的数据结构,我们可以通过树状数组讲问题”“改变 n次元素值,查询 n 次区间和”的总复杂度降为 O(nlogn)。

右图圆圈中标记有数字的节点,存储的是称为树状数组的tree[],一个结点上的tree[]的值,就是它树下的直连子节点的和,例如:

  • tree[1]=a1​
  • tree[2]=tree[1]+a2​
  • tree[3]=a3​
  • tree[4]=tree[2]+tree[3]+a4​
  • tree[8]=tree[4]+tree[6]+tree[7]+a8​

 我们利用tree【】,可以高效的完成下面两个操作:

  1. 查询,即求前缀和 sumsum,例如:

    • sum(8)=tree[8]
    • sum(7) = tree[7] + tree[6] + tree[4]
    • sum(6) = tree[6] + tree[4]

    右图中的虚线箭头是计算 sum(7) 的过程。显然,计算的复杂度是 O(logn) 的,这样就达到了快速计算前缀和的目的。

  2. 维护。tree[]本身的维护也是高效的。当元素 a 发生改变时,能以 O(logn) 的高效率修改 tree[] 的值。例如更新了a3​,那么只需要修改 tree[3]、tree[4]、tree[8]⋯,即修改它和它上面的那些结点:父结点以及父结点的父结点。

至于如何找到下一个节点,我们通过下面的lowbit()去寻找

下面我们介绍四个函数

lowbit(x):

找到一个数的二进制的最后一个1,为什么要找最后一个1呢?因为我们观察维护和查询这两个操作时,不难发现:

1.在查询的过程中,是每次去掉二进制的最后的1。例如求 sum(7) = tree[7] + tree[6] + tree[4],步骤是:

  • 7 的二进制是 111,去掉最后的 1,得 110,即 tree[6];
  • 去掉 6 的二进制 110 的最后一个 11,得 100,即 tree[4];
  • 4 的二进制是 100,去掉 1 之后就没有了。

2.在维护的过程中,是每次在二进制的最后的 1 上加 1。例如更新了 a3​,需要修改 tree[3]、tree[4]、tree[8]\cdotstree[8]⋯ 等等,步骤是:

  • 3 的二进制是 11,在最后的 1 上加上 1 得 100 ,即 44 ,修改 tree[4];
  • 4 的二进制是 100,在最后的 1 上加 1,得 1000,即 8,修改 tree[8];
  • 继续修改 tree[16]、tree[32]⋯ 等等。

而我们的lowbit()就可以通过二进制的位运算找到我们要修改的节点。其原理是利用了负数的补码表示,补码是原码取反加一。我们通过这个函数来定位我们将要修改的值。

update(x,d):

执行修改元素的功能,修改元素tree[x]=tree[x]+1,实际上在修改数组tree[],通过lowbit()找到下一个需要改变的树结点。直到x跳出最大值。

sum(x)

求前缀和a[1]+a[2]+a[3]+...+a[x]

discretize(lst):

离散化列表,把原来的数字,用他们的相对大小来替换原来的数值,而他们的顺序仍然不变,不影响逆序对的计算。

具体过程

我们先将树状数组初始化为0,再将输入的序列离散化,例如:

1 1234 23 234 3434 转化为1 4 2 3 5

再把数字看成树状数组的下标。每处理一个数字,树状数组的下标所对应的元素就+1,从后往前总计前缀和就是逆序对的数量,要参考上面的图。

我们从后往前遍历序列的时候,发现当前数x的前缀和sum(lists[i]-1)为几,则说明此时当前数的右边有几个比它小的数,我们就要在最终答案中加上sum(lists[i]-1),同时维护树状数组,updata(x,1),这里d我们只取1,意思是这个数被包含进了树状数组,在之后的查询中如果当前的序列中的数大于之前找到的数,就说明找到一对逆序对。

代码2

感谢蓝桥杯官网浪川博主大佬的主页

感谢蓝桥杯罗老师的教程

N=50010
tree=[0] * 50010

def lowbit(x):
    return x&-x

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

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


def discretize(lst):
    new_lists=sorted(lst)
    a=[0]
    for i in range(len(lst)):
      for j in range(1,len(lst)+1):
        if new_lists[j-1] == lst[i] and j not in a:
          a.append(j)
    return a

n=int(input())
lists=discretize(list(map(int,input().split())))

print(lists)

num=0
for i in range(len(lists)-1,0,-1):
  update(lists[i],1)
  num+=sum(lists[i]-1)

print(num)
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
### 回答1: 将数组中的数逆序存放,可以通过交换数组中对称位置上的元素来实现。具体步骤如下: 1. 定义一个数组,并初始化数组元素。 2. 定义两个变量i和j,分别表示数组的第一个元素和最后一个元素的下标。 3. 使用while循环,当i<j时,交换数组中i和j位置上的元素,并分别将i和j向中间移动一位。 4. 循环结束后,数组中的元素就被逆序存放了。 示例代码如下: int arr[] = {1, 2, 3, 4, 5}; int i = , j = sizeof(arr) / sizeof(int) - 1; while (i < j) { int temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; i++; j--; } // 输出逆序后的数组 for (int k = ; k < sizeof(arr) / sizeof(int); k++) { printf("%d ", arr[k]); } ### 回答2: 实验7-1-2要求我们将数组中的数逆序存放。所谓数组,就是在计算机内存中连续存放的一系列相同类型的数据。数组中每个数据项的位置都有一个下标,从0开始,依次递增。逆序存放就是将数组中的数据按照与原本相反的顺序存放。 具体来说,我们需要先定义一个长度为n的数组,将原始数据存放到这个数组中,然后从头到尾遍历数组,把第i个元素与倒数第i个元素互换位置即可。其中i的范围是[0, n/2)。这样一来,原数组中的第0个元素会和第n-1个元素互换位置,第1个元素会和第n-2个元素互换位置,以此类推,直到到达n/2,这样就完成了数组中的数逆序存放。 示例代码如下: ``` #include <stdio.h> #define MAX_SIZE 100 // 定义数组的最大长度 int main() { int a[MAX_SIZE]; // 定义整型数组 int n, i, temp; printf("请输入数组长度n(n<=100):"); scanf("%d", &n); printf("请输入数组元素:\n"); for (i = 0; i < n; i++) { scanf("%d", &a[i]); } // 将数组逆序存放 for (i = 0; i < n/2; i++) { temp = a[i]; a[i] = a[n-1-i]; a[n-1-i] = temp; } printf("逆序存放后的数组为:\n"); for (i = 0; i < n; i++) { printf("%d ", a[i]); } return 0; } ``` 以上代码中,首先定义了一个整型数组a和数组长度n。然后通过for循环依次输入数组元素,接着再通过for循环将数组逆序存放。最后再通过for循环依次输出逆序存放后的数组元素。注意,在输出数组元素时需要使用空格将每个元素隔开。 ### 回答3: 实验7-1-2是一道比较基础的题目,需要我们使用数组来存放一串数字,并将这些数字倒序存放。对于能够熟练掌握数组的人来说,这道题目应该并不难。 首先,我们需要创建一个数组来存放这些数字。假设我们需要存放的数字为:1、2、3、4、5,我们可以采用以下方式定义一个长度为5的数组来存放这些数字: int[] arr = {1, 2, 3, 4, 5}; 然后,我们可以使用一个for循环来遍历这个数组,并将数组中的数字逆序存放到一个新的数组中,如下所示: int[] newArr = new int[arr.length]; for (int i = arr.length - 1; i >= 0; i--) { newArr[arr.length - 1 - i] = arr[i]; } 在这个for循环中,我们首先创建了一个长度与原数组相同的新数组newArr,用来存放逆序后的数字。然后,从原数组的最后一个元素开始遍历,将遍历到的元素存放到新数组的第一个位置,以此类推逆序存放。 最后,我们可以打印出新数组中的数字,来验证我们的逆序存放是否正确: for (int i = 0; i < newArr.length; i++) { System.out.print(newArr[i] + " "); } 完整的代码如下所示: public class Main { public static void main(String[] args) { int[] arr = {1, 2, 3, 4, 5}; int[] newArr = new int[arr.length]; for (int i = arr.length - 1; i >= 0; i--) { newArr[arr.length - 1 - i] = arr[i]; } for (int i = 0; i < newArr.length; i++) { System.out.print(newArr[i] + " "); } } } 总之,实验7-1-2是一道比较简单的题目,需要我们掌握数组的基本操作,如定义、遍历、赋值等。在实际开发中,我们也会经常用到数组这种数据结构,因此熟练掌握数组的操作是非常重要的。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

晓宜

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

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

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

打赏作者

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

抵扣说明:

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

余额充值