Python(分治算法)问题 F: 求逆序对_给定一个序列a1,a2,…,an,如果存在i<j并且ai>aj,那么我们称之为逆序对,求逆序对的数目。

问题 F: 求逆序对

题目描述

给定一个序列a1,a2,…,an,如果存在i<j并且ai>aj,那么我们称之为逆序对,求逆序对的数目。
注意:n<=10^5,ai<=10^5

输入

第一行为n,表示序列长度。
接下来的n行,第i+1行表示序列中的第i个数。

输出

所有逆序对总数。

样例输入

4
3
2
3
2

样例输出

3

解答(分治算法):

def merge(a, lt, rt):
    mid = (lt + rt) // 2
    x = 0  # 统计逆序对
    tmp = [0] * (rt - lt + 1)
    li_1 = []
    i = lt  # 左区间起点
    j = mid + 1  # 右区间起点
    k = 0  # 表示第一个数后面的逆序对
    while mid >= i and j <= rt:
        if a[i] <= a[j]:
            tmp[k] = a[i]
            i += 1
        else:
            tmp[k] = a[j]
            j += 1
            x += mid - i + 1
            li_1.append(x)
        k += 1
    while mid >= i:
        tmp[k] = a[i]
        i += 1
        k += 1
    while rt >= j:
        tmp[k] = a[j]
        j += 1
        k += 1
    for i in range(k):
        a[lt + i] = tmp[i]
    return x


s = 0


def merge1(li, lt, rt):
    global s  # 全局变量s
    if rt > lt:  # lt==rt 表示只有一个数,退出
        mid = (lt + rt) // 2
        merge1(li, lt, mid)
        merge1(li, mid + 1, rt)
        s += (merge(li, lt, rt))
        return s


n = int(input())
li1 = []
for i in range(n):
    n1 = int(input())
    li1.append(n1)

print(merge1(li1, 0, len(li1) - 1))

答案不唯一,必定有更加优化的解法欢迎分享

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 题目描述: 给定一个序列a1,a2,…,an,如果存在i<j并且ai>aj,那么我们称之为对,对的数目。 解题思路: 1. 归并排 归并排是一种分治思想的排算法,它的基本思想是将待排序列分成若干个子序列,每个子序列都是有的,然后再将有的子序列合并成一个有序列。 在归并排的过程中,我们可以统计对的数量。具体来说,我们可以在合并两个有序列的时候,统计其中一个子序列中的元素a[i]与另一个子序列中的元素a[j](i<j)之间的对数量。因为这两个子序列都是有的,所以当a[i]>a[j]时,a[i]后面的所有元素都大于a[j],因此对的数量就是i之后的元素个数。 时间复杂度:O(nlogn) 空间复杂度:O(n) 2. 树状数组 树状数组是一种用于维护序列前缀和的数据结构,它可以在O(logn)的时间内完成单点修改和前缀和查询操作。 在对的过程中,我们可以使用树状数组维护序列中每个元素之前比它小的元素的个数。具体来说,我们可以将序列从大到小排,然后依次将每个元素插入到树状数组中,并查询它之前比它小的元素的个数,这个个数就是它的对数量。 时间复杂度:O(nlogn) 空间复杂度:O(n) 代码实现: 1. 归并排 ```python def merge_sort(nums): def merge(left, right): nonlocal cnt res = [] i, j = 0, 0 while i < len(left) and j < len(right): if left[i] <= right[j]: res.append(left[i]) i += 1 else: res.append(right[j]) j += 1 cnt += len(left) - i res += left[i:] res += right[j:] return res if len(nums) <= 1: return nums mid = len(nums) // 2 left = merge_sort(nums[:mid]) right = merge_sort(nums[mid:]) return merge(left, right) def count_inversions(nums): global cnt cnt = 0 merge_sort(nums) return cnt ``` 2. 树状数组 ```python class FenwickTree: def __init__(self, n): self.tree = [0] * (n + 1) def update(self, i, delta): while i < len(self.tree): self.tree[i] += delta i += i & -i def query(self, i): res = 0 while i > 0: res += self.tree[i] i -= i & -i return res def count_inversions(nums): sorted_nums = sorted(nums, reverse=True) rank = {num: i + 1 for i, num in enumerate(sorted_nums)} tree = FenwickTree(len(nums)) cnt = 0 for num in nums: cnt += tree.query(rank[num] - 1) tree.update(rank[num], 1) return cnt ``` ### 回答2: 对指的是,对于任意一个序列中的一对数aiaj,如果i<j且ai>aj,则这一对数就构成了一个对。对的数量是衡量一个序列中乱程度的重要指标,对于数据挖掘、机器学习和推荐系统等领域都有着广泛的应用。 对的数目可以用归并排来解决。在归并排的过程中,将原始序列递归地划分成若干个子序列,每次合并两个有序列时,可以通过统计左右两个子序列之间的对数量来计算当前子序列对数量。具体实现过程如下: 1. 定义一个计数器count,表示对数量 2. 定义一个辅助函数merge,用于合并两个有序列。假设left和right分别表示左右两个子序列,它们的长度分别为m和n,那么该函数的实现如下: ``` def merge(left, right): i, j, count = 0, 0, 0 res = [] while i < len(left) and j < len(right): if left[i] <= right[j]: res.append(left[i]) i += 1 else: res.append(right[j]) j += 1 count += len(left) - i res += left[i:] res += right[j:] return res, count ``` 3. 使用归并排将原始序列,并计算对数量。假设原始序列为a,该函数的实现如下: ``` def merge_sort(a): if len(a) <= 1: return a, 0 mid = len(a) // 2 left, x = merge_sort(a[:mid]) right, y = merge_sort(a[mid:]) res, z = merge(left, right) return res, x + y + z ``` 其中,merge_sort函数首先判断序列是否为空或只有一个元素,如果是则直接返回;否则将序列划分为左右两个子序列,并递归调用merge_sort函数,最后通过调用merge函数将左右两个子序列合并为一个有序列,并计算其中的对数量。 4. 最后返回merge_sort函数的结果即可。对的数量即为该函数的第二个返回值。 该算法的时间复杂度为O(nlogn),空间复杂度为O(n)。 ### 回答3: 对是指在一个序列中,若前面的数比后面的数大,则这两个数构成一个对。对的数目是经典的数学问题,也是算法设计中的一个重要问题之一。 一个朴素的做法是,遍历每对数对,判断是否为对,时间复杂度为O(n^2),显然不是一个好的算法。更好的做法是基于归并排的思想,即归并排的“分治”和“合并”两个步骤。具体来说,对于一个给定序列,先将其分为两部分,对每一部分进行递归地分解,直到子部分不能再分,也就是只有一个元素。然后将这些子部分“合并”起来,得到新的有序列。在合并过程中,如果一个元素在左子部分序列中,其下标为i,在右子部分序列中,其下标为j,且左子部分需要调用前k-1个元素,右子部分需要调用前l-1个元素才能得到有序列,则i,j之间包含l-1-k个对。这是因为左右两部分元素按合并时,当前左子部分遍历到i,右子部分遍历到j,如果此时左子部分当前元素a[i] > 右子部分当前元素a[j],则左子部分从a[1]到a[i-1]都大于a[j],因此有l-j个数与a[j]构成对;同时,左右子部分的元素都是有的,因此左子部分从a[1] 到 a[i-1],右子部分从a[1]到a[j-1]都是小于a[j]的,因此共有i-1-k个数与a[j]构成对。 归并排的时间复杂度为O(nlogn),因此对的时间复杂度也是O(nlogn)。''' 举例说明:序列23 12 3 56 78 (1) 23 12 3 56 78 (2) 23 12 3 | 56 78 (3) 23 12 | 3 | 56 78 (4) 23 | 12 | 3 | 56 | 78 对于(4),左部分的(23),右部分的(12),(3),分别组成(23,12),(23,3),(12,3),形成了3个 对于(3),左部分的(23),右部分的(3),分别组成(23,3),形成了1个 对于(2),左部分的(23,12,3),右部分的(56,78),形成了3个 对于(1),左部分的(23,12,3,56,78),右部分的(),没有 综上所述,该序列中共有7个,因此数目为7。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值