求逆序数两种方法:线段树/归并排序

题目:HDU 4911/poj 2299

第一反应是暴力扫一遍,时间复杂度O(N^2),这在处理10^5的数量级的数据时一定会超时。以下给出两种将时间复杂度优化至O(NlogN)的方法

以HDU 4911为例,求逆序数的模板题

方法1,归并排序法:

在区间[l,r]上,mid=(l+r)>>1;

将两串已经排好序的序列a[l...m]与a[m+1...r]进行归并排序。设l<=i<=mid<j<=tail

当a[j]>=a[i]时,后面的数大于前面的数,不产生逆序对,直接进行归并即可(tmp[k++]=a[i++]),当a[i]>a[j]时,表示前面的数大于后面的数,产生了逆序对。此时i~m的数均比a[j]大,即,产生m-i+1个逆序对。故逆序对总数应加上(m-i+1)

代码:

//By Sean Chen
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>

using namespace std;

int n,k;
long long cnt;
int a[100005],tmp[100005];

void Merge(int head, int mid, int tail)     //二路归并
{
    int i = head, j = mid + 1, k = head;
    while (i <= mid && j <= tail)
    {
        if (a[i] > a[j])        //前面的数大于后面的数,将后面的数加入归并后的序列,并增加逆序对
        {
            cnt += mid - i + 1;
            tmp[k++] = a[j++];
        } 
        else           //后面的数大于前面的数,直接将前面的数加入归并后的序列
        {
            tmp[k++] = a[i++];
        }
    }
    while (i <= mid)
    {
        tmp[k++] = a[i++];
    }
    while (j <= tail)
    {
        tmp[k++] = a[j++];
    }
    for (int i = head; i <= tail; i++)
    {
        a[i] = tmp[i];
    }
    return;
}

void Merge_Sort(int head, int tail)
{
    if (tail > head)
    {
        int mid = (head + tail) >> 1;
        Merge_Sort(head, mid);
        Merge_Sort(mid+1, tail);
        Merge(head, mid, tail);
    }
    return;
}

int main()
{
    while (scanf("%d%d",&n,&k) != EOF)
    {
        for (int i = 0; i < n; i++)
        {
            scanf("%d",&a[i]);
        }
        cnt = 0;
        Merge_Sort(0, n-1);
        if (cnt <= k)
            cout << 0 << endl;
        else
            cout << cnt-k << endl;
    }
    return 0;
}
方法二:线段树+离散化

通过线段树,不断统计当前数字之前比其大的数字的个数,并将当前数字加入线段树,直至最后一个即可。方法较为容易理解,在暴力的基础上,对统计个数的方法进行了优化。注意,由于数字大小到达了10^9,所以需要进行离散化。

//By Sean Chen
#include <iostream>
#include <cstring>
#include <cstdio>
#include <cmath>
#include <algorithm>

using namespace std;
const int maxn = 500500;

int n, a, b, in[maxn], tt[maxn], tmp[maxn], f[maxn], k, q;
long long num[maxn*3];

int bs(int v, int x, int y)          //二分查找当前的数字的编号
{
    while(x < y)
    {
        int m = (x+y) >> 1;
        if(tmp[m] >= v)
            y = m;
        else
            x = m+1;
    }
    return x;
}

void build(int pos, int l, int r)
{
    num[pos] = 0;
    if(l == r) return ;
    int m = (l+r) >> 1;
    build(pos<<1, l, m);
    build(pos<<1|1, m+1, r);
}

void update(int pos, int l, int r)
{
    if(l == r)
    {
        num[pos]++;
        return ;
    }
    int m = (l+r) >> 1;
    if(a <= m)
        update(pos<<1, l, m);
    else
        update(pos<<1|1, m+1, r);
    num[pos] = num[pos<<1] + num[pos<<1|1];
}

long long query(int pos, int l, int r)
{
    if (a <= l && r <= k-1)
        return num[pos];
    int m = (l+r) >> 1;
    long long ans = 0;
    if (a <= m)
        ans += query(pos<<1, l, m);
    if (m < k-1)
        ans += query(pos<<1|1, m+1, r);
    return ans ;
}

int main()
{
    while(scanf("%d%d",&n,&q)!=EOF)
    {
        for(int i = 0; i < n; i++)
        {
            scanf("%d",&in[i]);
            tt[i] = in[i];        //记录原序列
        }
        sort(in, in+n);
        k = 0;
        tmp[k++] = in[0];
        for(int i = 1; i < n; i++)            //离散化
            if(in[i] != in[i-1])
                tmp[k++] = in[i];
        for(int i = 0; i < n; i++)
        {
            f[i] = bs(tt[i], 0, k-1);
        }
        long long ans = 0;
        build(1, 0, k-1);

        for(int i = 0; i < n; i++)       //逐个统计之前比他大的数并加入线段树
        {
            a = f[i] + 1;
            ans += query(1, 0, k-1);
            a = f[i];
            update(1, 0, k-1);
        }
        if (ans > q)
            cout << ans-q << endl;
        else
            cout<< 0 <<endl;
    }
    return 0;
}

代码:


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值