【留坑】UVA10810 求逆序对==求最少相邻元素交换次数 归并排序OR线段树OR树状数组

本文介绍了一种通过计算数列中逆序对数量来确定将数列排序所需的最少交换次数的方法。利用归并排序过程中的合并步骤,有效地计算出逆序对的数量。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1 题意:

给出一个数列,允许相邻元素进行交换,求最少交换次数。

2分析:

求最少交换次数,等同于求该数列中的逆序对数。 

证明:先将最大的数与后面的数交换直到停止,交换次数=该数的逆序数,然后将次大数与后面的数交换直到停止,交换次数=次大数的逆序数,如此直到最小数,则总共的交换次数=整个数列的逆序对数。 

代码一:归并排序

代码二和三,等复习完数再补

3

代码一:

#include <iostream>
#include <string.h>
#include <stdio.h>
#include <algorithm>
#define ll long long
using namespace std;

const int maxn=500010;
ll a[maxn],t[maxn],ans;
void Merge_sort(int l,int r){
    ///1 回溯:(递归基)
    if(l==r)    return ;

    ///2 递归:
    int mid=(l+r)>>1;//(右移,向下取整)
    Merge_sort(l,mid);
    Merge_sort(mid+1,r);
    int i=l,j=mid+1,now=0;

    ///3 合并:
    while(i<=mid&&j<=r){
        if(a[i]>a[j]){//归并排序是稳定排序,所以不写>=
            ans+=(mid-i+1);//不要写ans+=(j-mid-1) 这个有延迟要再加几行代码
            t[++now]=a[j++];
        }
        else{
            t[++now]=a[i++];
        }
    }
    while(i<=mid)//(处理左右子区间中、指针未指到最后所剩下的元素)
        t[++now]=a[i++];
    while(j<=r)
        t[++now]=a[j++];

    ///4 复制:
    now=0;//(将两个子区间、经过有序+合并处理后得到的t[],赋值到对应位置的a[]中)
    for(int k=l;k<=r;k++)
        a[k]=t[++now];
}
int main()
{
    int n;
    while(~scanf("%d",&n)&&n!=0){
        ans=0;
        for(int i=1;i<=n;i++)
            scanf("%d",&a[i]);
        Merge_sort(1,n);
        cout<<ans<<endl;
    }
}


<think>好的,我现在需要帮助用户了解如何用归并排序来计算组中的逆序对。首先,我得回忆一下归并排序的基本原理,因为用户可能已经知道,但可能需要复习一下。归并排序是分治法的典型应用,通过将组分成子组,排序后再合并。在这个过程中,合并阶段可以统计逆序对量。 接下来,我要明确逆序对的定义:如果前面一个字大于后面的字,那么这两个字就组成一个逆序对。比如组[3,1,2]中,逆序对是(3,1)和(3,2),所以总是2。 接下来,我需要思考如何在归并排序的合并过程中统计逆序对归并排序在合并两个已排序的子组时,会比较左右两边的元素。当左边的元素大于右边的元素时,这里可能存在多个逆序对。例如,如果左边子组是[4,5],右边是[1,3],当合并时,4比1大,那么4和后面的所有右边元素都构成逆序对,这里就是2个逆序对(4和1,4和3)。 这时候,我需要考虑如何具体实现这一点。在合并过程中,每次从左右两个子组中取出较小的元素放入临时组。当右半部分的元素被取出时,说明当前左半部分剩余的所有元素都比这个右半部分的元素大,因此可以累加逆序对量。比如,当合并到右半部分的元素1时,左半部分剩下的元素是4和5,所以此时逆序对量增加2。 然后,我需要考虑分治法的步骤。递归地将组分成左右两部分,分别计算左右内部的逆序对,然后在合并时计算跨越左右的逆序对。最后将这三部分的结果相加。这样就能在O(n log n)的时间复杂度内解决问题。 接下来,我需要考虑具体的代码实现。归并排序通常需要一个辅助组来合并,而在统计逆序对时,需要在合并过程中添加计逻辑。比如,当右边的元素被选入辅助组时,计器加上左半部分剩余元素的个。 还要注意边界条件和递归终止条件,比如当子组长度为1时,逆序对量为0。另外,需要考虑组索引的处理,避免越界错误。 然后,我需要验证这个方法的正确性。例如,对于一个简单的测试用例,如[3,1,2],归并排序在合并过程中应该正确统计出2个逆序对。或者更复杂的例子,比如[7,5,6,4],逆序对应该是5。 最后,需要总结这个过程,确保用户能够理解分治法的应用,以及如何在归并排序的合并阶段统计逆序对量。同时,可能还要比较归并排序与其他方法(如暴力法)的时间复杂度,说明归并排序的优势。</think>### 使用归并排序计算逆序对的原理与实现 #### 核心思想 归并排序的分治特性天然适合统计逆序对。在合并两个有序子组时,通过比较左右两部分的元素,可以**直接计算跨越左右子组的逆序对量**,再递归统计子组内部的逆序对[^1][^3]。 #### 关键步骤 1. **分治递归** 将组分成左右两部分,分别递归计算左右子组内部的逆序对量: $$ 总逆序对 = 左子逆序对 + 右子逆序对 + 合并时发现的逆序对 $$ 2. **合并计** 合并两个有序子组时,当发现右子元素小于左子元素时,说明当前左子组中所有未处理的元素都与该右子元素构成逆序对。例如: ``` 左子组:[5,7], 右子组:[4,6] 当右子元素4被取出时,左子组剩余元素5、7都 > 4,此时累加2个逆序对 ``` #### 代码实现 ```python def merge_sort_count(arr): if len(arr) <= 1: return arr, 0 mid = len(arr) // 2 left, cnt_left = merge_sort_count(arr[:mid]) right, cnt_right = merge_sort_count(arr[mid:]) merged, cnt_cross = merge(left, right) return merged, cnt_left + cnt_right + cnt_cross def merge(left, right): merged = [] i = j = cnt = 0 while i < len(left) and j < len(right): if left[i] <= right[j]: merged.append(left[i]) i += 1 else: merged.append(right[j]) cnt += len(left) - i # 核心计点 j += 1 merged += left[i:] merged += right[j:] return merged, cnt ``` #### 算法特性 - 时间复杂度:$O(n\log n)$,与归并排序时间复杂度一致[^2] - 空间复杂度:$O(n)$,来自合并时的临时组[^3] - 优势:相比暴力解法的$O(n^2)$时间复杂度,效率显著提升
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值