HDU 1394 Minimum Inversion Number(求逆序对+线段树||归并排序)

本文介绍了一种求解逆序对数量的方法,并通过实例演示如何利用线段树来高效解决逆序对相关的问题。文章首先定义了逆序对的概念,接着详细解释了如何使用归并排序计算逆序对的数量,并进一步展示了如何借助线段树优化这一过程。

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

 Problem Description
The inversion number of a given number sequence a1, a2, ..., an is the number of pairs (ai, aj) that satisfy i < j and ai > aj.

For a given sequence of numbers a1, a2, ..., an, if we move the first m >= 0 numbers to the end of the seqence, we will obtain another sequence. There are totally n such sequences as the following:

a1, a2, ..., an-1, an (where m = 0 - the initial seqence)
a2, a3, ..., an, a1 (where m = 1)
a3, a4, ..., an, a1, a2 (where m = 2)
...
an, a1, a2, ..., an-1 (where m = n-1)

You are asked to write a program to find the minimum inversion number out of the above sequences.
 

Input
The input consists of a number of test cases. Each case consists of two lines: the first line contains a positive integer n (n <= 5000); the next line contains a permutation of the n integers from 0 to n-1.
 

Output
For each case, output the minimum inversion number on a single line.
 

Sample Input
10 1 3 6 9 0 8 5 7 4 2
 

Sample Output
16

解题思路:

求逆序对的进阶版,这是线段树专题,我一开始就用归并排序ac了比他们都快hhhhh,不过线段树还是要好好学的,百度搜了一个解法,可惜没备注,理解了我一个下午,加上我自己理解把代码奉上吧,不知道有没有理解对,引用:逆序数求得之后,把第一个数移到最后的逆序数是可以直接得到的,比如原来的逆序数是ans,把a[0]移到最后后,减少逆序数a[0],同时增加逆序数n-a[0]-1个,就是ans-a[0]+n-a[0]-1,然后循环求最小值就行了。


代码:

先是归并排序,花的时间居然比线段树少:

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<math.h>
#include<string>
#include<stdio.h>
#include<queue>
#include<stack>
#include<map>
using namespace std;
int a[5005],b[5005],n,ans;
void Merge(int l,int r,int mid)
{
    int t[5005],i=l,j=mid+1,k=l;
    while(i<=mid&&j<=r)
    {
        if(a[i]>a[j])
        {
            t[k]=a[j];
            ans+=mid-i+1;
            k++;
            j++;
        }
        else
        {
            t[k]=a[i];
            i++;
            k++;
        }
    }
    while(i<=mid)
    {
        t[k]=a[i];
        k++;
        i++;
    }
    while(j<=r)
    {
        t[k]=a[j];
        k++;
        j++;
    }
    for(i=l;i<=r;i++)
        a[i]=t[i];
}
void guibin(int l,int r)
{
    if(l>=r)
        return;
    int mid=(l+r)/2;
    guibin(l,mid);
    guibin(mid+1,r);
    Merge(l,r,mid);
}
int main()
{
    int i,j;
    while(scanf("%d",&n)!=EOF)
    {
        for(i=1;i<=n;i++)
        {
            scanf("%d",&a[i]);
            b[i]=a[i];
        }
        ans=0;
        guibin(1,n);
        int minn=ans;
        for(i=1;i<=n;i++)
        {
            ans+=n-b[i]-b[i]-1;
            if(ans<minn)
                minn=ans;
        }
        printf("%d\n",minn);
    }
    return 0;
}


然后是线段树代码:

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<math.h>
#include<string>
#include<stdio.h>
#include<queue>
#include<stack>
#include<map>
using namespace std;
struct node
{
    int l,r;
    int num;
}t[20005];//线段树大小等于数据范围*4
int a[5005],n;
void Build(int l,int r,int num)//递归建树
{
    t[num].l=l;
    t[num].r=r;
    t[num].num=0;
    if(l==r)
        return;
    int mid=(l+r)/2;
    Build(l,mid,num*2);
    Build(mid+1,r,num*2+1);
}
int Find(int num,int x,int y)//访问函数,查找[x,y]区间内有多少已经存在的数
{
    int l=t[num].l;
    int r=t[num].r;
    if(l==x&&r==y)
    {
        return t[num].num;
    }
    int mid=(l+r)/2;
    int s=0;
    if(x<=mid)
        s+=Find(num*2,x,min(y,mid));//如果一部分线段在t[num]的左边,访问左区间
    if(y>mid)
        s+=Find(num*2+1,max(mid+1,x),y);//如果一部分线段在t[num]的右边,访问右区间
    return s;
}
void update(int num,int x)
{
    t[num].num++;//把包含x的所有区间的数目加一
    int l=t[num].l;
    int r=t[num].r;
    if(l==r)
    {
        return;
    }
    int mid=(l+r)/2;
    if(x<=mid)
        update(num*2,x);
    else
        update(num*2+1,x);
}
int main()
{
    int i,j,ans;
    while(scanf("%d",&n)!=EOF)
    {
        ans=0;
        Build(1,n,1);
        for(i=1;i<=n;i++)
        {
            scanf("%d",&a[i]);
            ans+=Find(1,1,a[i]+1);//在[1,a[i]+1]区间内查找之前已经存在了的比a[i]小的数的和,a[i]+1是因为从0开始
            update(1,a[i]+1);//添加了一个节点以后线段树的区间更新
        }
        ans=n*(n-1)/2-ans;//所有可能对数减去顺序对数为逆序对数
        int minn=ans;
        for(i=1;i<=n;i++)
        {
            ans+=n-a[i]-a[i]-1;//由推出的公式求出后面的逆序对数
            if(ans<minn)
                minn=ans;
        }
        printf("%d\n",minn);
    }
    return 0;
}



逆序数求得之后,把第一个数移到最后的逆序数是可以直接得到的。
比如原来的逆序数是ans,把a[0]移到最后后,减少逆序数a[0],同时增加逆序数n-a[0]-1个
就是ans-a[0]+n-a[0]-1;
 
只要i从0-n-1循环一遍取最小值就可以了。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值