poj 2299 求逆序數 樹狀數組/歸併排序

POJ - 2299


目錄

一、用樹狀數組求逆序數 

二、用歸併排序求逆序數


關於樹狀數組知識點的鏈接: 樹狀數組

 

一、用樹狀數組求逆序數 

1、對樹狀數組的理解

現在算上比較明白樹狀數組是什麼意思了,其實C[ ]數組就相當於線段樹中的線段,C[ ]表示的是有幾個葉子節點(A[ ])相加。按我的理解 我就把C[ ]數組理解成線段。 而add(int x,int num)函數叫單點修改,爲什麼叫單點修改呢,其實意思就是在葉子節點A[x]處增加num,所以這麼一修改會影響到C[ ]數組,也就是含A[x]葉子節點的線段,所以在函數中要修改C[ ]數組,維護C[ ]數組。而sum()函數的意思就簡單了,sum(x)其實就是查詢從葉子節點A[1]到A[x]的和(這裏認爲葉子節點的座標從1開始)。
A[ ]數組其實是我們在分析過程中假想的葉子節點數組,在實際應用中其實不用創建A數組。

2、用樹狀數組求逆序數的理解

而對於這道題呢,讓我們求一段排列的逆序數,就可以用到上面樹狀數組的知識點來求解。首先對原始數據離散化之後(離散化的理解在下面),我們就按排列的順序依次將元素插入到樹狀數組中(其實意思就是要進行單點更新)剛開始每個葉子節點都爲0 意思就是序列中還沒有這個元素。然後我們依次add(x,1)加入到數組中,1是什麼意思呢,意思就是代表序列中有了這個元素,同時也是方便之後的sum求和。 每次add(x,1),我們就算一遍sum(x),求下標從1—x的和,所以sum(x)在這道題中的實際意義就變成了,計算我前面插入的數中小於等於x的個數, 所以i- sum( x )的意思就是大於x的個數,即逆序數!! for循環結束之後就算好了整個序列的逆序數。

爲什麼離散化,因爲我們在用樹狀數組時,用add(int x,int num)函數往數組中插入num元素時,在本題中元素的數據範圍很大,導致我們們就要開999,999,999大小的數組,(爲什麼要開這麼大的,因爲我們用sum(x)函數計算比x小的個數時,肯定要計算出x數值前所有的和,所以最大就要開999,999,999大小額數組)。所以我們就採用離散化的思想,將原始序列數據9,1,0,5,4轉化成了5,2,1,4,3(如何轉化的見下面的博客鏈接)。所以就不會受到很大的元素的影響了。

 

一篇很好的題解鏈接

#include<iostream>
#include<stdio.h>
#include<cstring>
#include<algorithm>
#include<queue>
#include<string>
#include<set>
using namespace std;
const int maxn=500000+10;
int n;
struct node
{
    long long val;
    int no;
    bool operator <(const node &t1) const{
        return val<t1.val;
    }
};
node t[maxn];
int c[maxn];
int a[maxn];//存離散化之後的數據
int lowbit(int x){return x&(-x);}
void add(int x,int num)
{
    for(int i=x;i<=n;i+=lowbit(i))
        c[i]+=num;
}
int sum(int x)
{
    int ans=0;
    for(int i=x;i>0;i-=lowbit(i))
        ans+=c[i];
    return ans;
}
int main()
{
//    freopen("in.txt","r",stdin);
    ios::sync_with_stdio(false);
    while(cin>>n&&n)
    {
        memset(t,0,sizeof(struct node)*maxn);
        memset(c,0,sizeof(c));
        memset(a,0,sizeof(a));
        for(int i=1;i<=n;i++)
        {
            cin>>t[i].val;
            t[i].no=i;
        }
        /*離散化*/
        sort(t+1,t+n+1);
        for(int i=1;i<=n;i++)
            a[t[i].no]=i;
        long long ans=0;
        for(int i=1;i<=n;i++)
        {
            add(a[i],1);
            ans+=i-sum(a[i]);//計算當前序列中大於a[i]的個數
        }
        cout<<ans<<endl;
    }
    return 0;
}

 

二、用歸併排序求逆序數

逆序數百度百科

歸併排序-求逆序數算法 看到一篇講的很好的用歸併排序求逆序數的博客。裏面講的很清楚了。

 

#include<iostream>
#include<stdio.h>
#include<cstring>
#include<algorithm>
#include<queue>
#include<string>
#include<set>
using namespace std;
const int maxn=500000+10;
int n;
long long ans;
int a[maxn];//原始數組
int b[maxn];//臨時數組
long long Merge(int low,int mid,int high)
{
    int i=low,j=mid+1,k=low;
    long long num=0;
    while(i<=mid&&j<=high)
    {
        if(a[i]<=a[j])
            b[k++]=a[i++];
        else
        {
            b[k++]=a[j++];
            num+=j-k;//表示合併時之間存在的逆序對數
        }
    }
        while(i<=mid)
            b[k++]=a[i++];
        while(j<=high)
            b[k++]=a[j++];
        for(int i=low;i<=high;i++)
            a[i]=b[i];
    return num;

}
long long MergeSort(int l,int r)
{
    if(l<r)
    {
        int mid=(l+r)/2;
        long long num=0;
        num+=MergeSort(l,mid);//先分,即先排序
        num+=MergeSort(mid+1,r);
        num+=Merge(l,mid,r);//再治,即合併
        return num;
    }
    return 0;
}
int main()
{
//    freopen("in.txt","r",stdin);
    ios::sync_with_stdio(false);
    while(cin>>n&&n)
    {
        ans=0;
        memset(a,0,sizeof(a));
        memset(b,0,sizeof(b));
        for(int i=0;i<n;i++)
            cin>>a[i];
        ans=MergeSort(0,n-1);
        cout<<ans<<endl;
    }
    return 0;
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章