洛谷P1908-逆序对(多种方法:归并排序 / 线段树 / 树状数组)

题目描述

猫猫 TOM 和小老鼠 JERRY 最近又较量上了,但是毕竟都是成年人,他们已经不喜欢再玩那种你追我赶的游戏,现在他们喜欢玩统计。

最近,TOM 老猫查阅到一个人类称之为“逆序对”的东西,这东西是这样定义的:对于给定的一段正整数序列,逆序对就是序列中 a i > a j a_i>a_j ai>aj i < j ii<j 的有序对。知道这概念后,他们就比赛谁先算出给定的一段正整数序列中逆序对的数目。注意序列中可能有重复数字。

Update:数据已加强。

输入格式

第一行,一个数 n n n,表示序列中有 n n n个数。

第二行 n n n 个数,表示给定的序列。序列中每个数字不超过 1 0 9 10^9 109

输出格式

输出序列中逆序对的数目。

样例 #1

样例输入 #1

6
5 4 2 6 3 1

样例输出 #1

11

提示

对于 25 % 25\% 25% 的数据, n ≤ 2500 n \leq 2500 n2500

对于 50 % 50\% 50% 的数据, n ≤ 4 × 1 0 4 n \leq 4 \times 10^4 n4×104

对于所有数据, n ≤ 5 × 1 0 5 n \leq 5 \times 10^5 n5×105

请使用较快的输入输出

应该不会 O ( n 2 ) O(n^2) O(n2) 过 50 万吧 by chen_zhe

分析

题目要求数列中逆序对的数量,比如2 1就是一对逆序对。

方法1:归并排序

最简单的方法,在归并排序中,逆序对的数量就是 mid-p+1。

#include
#define int long long
using namespace std;
const int N=1e6+5;
int a[N],help[N];
int ans;
void merge_sort(int l,int r)
{
    if(l<r)
    {
        int mid=(l+r)/2;
        merge_sort(l,mid);
        merge_sort(mid+1,r);
        int k=l;
        int p=l;
        int q=mid+1;
        while(p<=mid&&q<=r)
        {
            if(a[p]<=a[q])
            //等于的时候ans不应该更新,所以这边条件一定要加等号
            {
                help[k++]=a[p++];
            }
            else{
                ans+=mid-p+1;//只有完全大于的时候ans才需要更新
                help[k++]=a[q++];
            }
        }
        while(p<=mid) help[k++]=a[p++];
        while(q<=r) help[k++]=a[q++];

        for(int i=l;i<=r;i++)
        a[i]=help[i];
    }
}
signed main()
{
    int n;
    cin>>n;
    for(int i=1;i<=n;i++)
    cin>>a[i];
    merge_sort(1,n);
    cout<<ans;
}

方法2:树状数组

数据规模过大,达10e9,所以使用结构体来存放,定义结构体存放数据的值val和数据的编号id。
使用sort函数排序,排序之后val是降序的,id是无序的,这时候只需要遍历id,将id依次放入树状数组,读取比id小的值的数量,这些数量相加就是逆序对的数量。

#include
#include
#define lowbits(x) x&(-x)
#define int long long
using namespace std;
const int N=1e6+5;
struct node{
    int id,val;
}tr[N];
int n;
int tree[N];
bool cmp(node x,node y)
{
    if(x.val==y.val) return x.id>y.id;
    return x.val>y.val;
}
void updata(int x,int y)
{
    for(int i=x;i<=n;i+=lowbits(i))
    {
        tree[i]+=y;
    }
}
int query(int x)
{
    int sum=0;
    while(x)
    {
        sum+=tree[x];
        x-=lowbits(x);
    }
    return sum;
}
signed main()
{
    cin>>n;
    for(int i=1;i<=n;i++)
    {
        cin>>tr[i].val;
        tr[i].id=i;
    }
    sort(tr+1,tr+1+n,cmp);
    int ans=0;
    for(int i=1;i<=n;i++)
    {
        updata(tr[i].id,1);
        ans+=query(tr[i].id-1);
    }
    cout<<ans;
}

方法3:线段树

想法和树状数组一样,先把线段树的值初始化为0,接着依次修改id的在线段树中值为1,只需要统计小于id的数量就可以了

#include
#include
#define int long long
using namespace std;
const int N=1e6+5;
struct node{
    int l,r,val;
}tr[N*4];
struct node2{
    int id,val;
}a[N];
int n;
bool cmp(node2 x,node2 y)
{
    if(x.val==y.val) return x.id>y.id;
    return x.val>y.val;
}
void pushup(int u)
{
    tr[u].val=tr[u*2].val+tr[u*2+1].val;
}
void build(int u,int l,int r)
{
    if(l==r){
        tr[u]={l,r,0};//线段树初始化为0
        return ;
    }
    int mid=(l+r)/2;
    build(u*2,l,mid);
    build(u*2+1,mid+1,r);
    tr[u]={l,r};
    pushup(u);
}
void modify(int u,int x,int k)
{
    if(tr[u].l==tr[u].r)
    {
        tr[u].val+=k;
        return ;
    }
    int mid=(tr[u].l+tr[u].r)/2;
    if(x<=mid) modify(u*2,x,k);
    else modify(u*2+1,x,k);
    pushup(u);
}
int query(int u,int l,int r)
{
    if(l>tr[u].r||r<tr[u].l) return 0;
    if(tr[u].r<=r&&tr[u].l>=l) return tr[u].val;
    
    return query(u * 2, l, r)+query(u * 2 + 1, l, r);
}
signed main()
{
    cin>>n;
    for(int i=1;i<=n;i++)
    cin>>a[i].val,a[i].id=i;
    sort(a+1,a+1+n,cmp);
    build(1,1,n);
    int ans=0;
    for(int i=1;i<=n;i++)
    {
        modify(1,a[i].id,1);//修改值为1
        ans+=query(1,1,a[i].id-1);
    }
    cout<<ans<<endl;
}

你可能感兴趣的:(算法,数据结构,c++)