分治入门——数列分治

分治思想:划分子问题,解决子问题,合并子问题

题目:输入一个整数n,然后输入n个整数。若是将n个整数进行冒泡排序(升序),那么请问排完序之后将会发生多少次的数据交换即求序列中存在的逆序对的个数?(1<=n<=100000)

题解:虽然题目说是冒泡,但是冒泡的复杂度是O(n^2),所以必然超时。因此这里使用分治的思想即简单修改一下归并排序。步骤如下:
(1)将原序列命名序列A,现在问题为求序列A的逆序对个数并使A序列升序
(2)将序列A取中间为分界线划分出左右两个子序列B,C。任意(i,j)满足逆序存在三种可能性

①i,j均属于B
②i,j均属于C
③i 属于B,j 属于C

(3)①②为子问题,可由递归解得,对于③,则只需要对C序列中每一个数字,统计B中比它大的数字的个数,累加起来即可

时间复杂度:每次递归长度都会减半,因此递归的深度为O(logn),每一层的时间复杂度为O(n),所以总的时间复杂度为O(nlogn)

代码如下:

#include<bits/stdc++.h>
typedef long long ll;
using namespace std;
const int N = 1e5+5;

int a[N], ans[N];

ll solve(int l, int r)
{
    //划分并解决子问题
    int mid = (l+r)>>1;
    if(l==r)    return 0;
    ll num = 0;//逆序对的个数

    num += solve(l, mid);
    num += solve(mid+1, r);

    //合并子问题

    //每一次的处理结果,升序保存在ans数组
    //将属于不同子序列的逆序对个数累加
    for(int i = l, j = mid+1, k = 0; i<=mid||j<=r; k ++)
    {
        if(i>mid)   ans[k] = a[j++];
        else if(j>r)    ans[k] = a[i++];
        else if(a[i]<=a[j]) ans[k] = a[i++];
        else
        {
            //出现逆序对
            ans[k] = a[j++];
            num += mid-i+1;//B序列中大于a[j]的个数
        }
    }
    for(int i = 0; i <= (r-l); i ++)
        a[l+i] = ans[i];
    return num;
}

int main()
{
    int n;
    scanf("%d", &n);
    for(int i = 0; i < n; i ++)
        scanf("%d", a+i);
    printf("%lld\n",solve(0, n-1));
    return 0;
}

猜你喜欢

转载自blog.csdn.net/weixin_38287798/article/details/79111335