1. 程式人生 > >用歸併排序或樹狀陣列求逆序對數量 poj2299

用歸併排序或樹狀陣列求逆序對數量 poj2299

題目連結:https://vjudge.net/problem/POJ-2299

推薦講解樹狀陣列的部落格:https://blog.csdn.net/int64ago/article/details/7429868

題目意思就是讓我們把無序的一些數字經過相鄰數字間兩兩交換,最後變成不遞減的數字。我們要求出最少要交換的次數。

逆序對(百度的):對於一個包含N個非負整數的陣列,A[1..n],A[1..n],如果有i < j,且A[ i ]>A[ j ],則稱(A[ i] ,A[ j] )為陣列A中的一個逆序對。

瞭解了一下樹狀陣列就過來做這題了,一開始用map容器,一直TLE,後面看了論壇,發現不用map容器編號就可以過,並且還可以用歸併排序來寫,這兩種方法都可以用來求逆序對數量。

樹狀陣列:

因為數字最大可以達到999999999,我們用陣列是存不下來的,所以我們要給每一個數字編號,

假設現在就是        9 1 0 5 4

那麼我給它們編一個號碼就是    1 2 3 4 5    狀態一

我們要把它們用兩兩交換的方法來進行排序,排序之後就是  0 1 4 5 9

對應的編號就是                     3 2 5 4 1  狀態二

那現在問題就可以看成我們把編號1到5從狀態一用相鄰數字交換點的方法變換到狀態二,它等同於從狀態二變換到狀態一,我們現在就討論從狀態二變換到狀態一需要的步數就可以了,那麼我們看狀態二,從左到右列舉每一個數字,每次加上它前面比他大的數字的數量(就相當於把他交換到所有比他大的數字前面),這個用樹狀數組裡面求區間和的操作就可以了,假設現在編號是k,那麼加的數量就是函式sum(n)-sum(k)。恩,沒了。

程式碼:

#include<iostream>
#include<cstring>
#include<algorithm>
#include<queue>
#include<map>
#include<stack>
#include<cmath>
#include<vector>
#include<set>
#include<cstdio>
#include<string>
#include<deque> 
using namespace std;
typedef 
long long LL; #define eps 1e-8 #define INF 0x3f3f3f3f #define maxn 500005 struct node{ int index,value;//編號和一開始的值 }a[maxn]; LL b[maxn],ans;//要用long long int n; bool cmp(node s1,node s2){ return s1.value<s2.value; } int lowbit(int x){ return x&(-x); } LL sum(int x){ LL ans=0; while(x>0){ ans+=b[x]; x-=lowbit(x); } return ans; } void add(int x){ while(x<maxn){ b[x]++; x+=lowbit(x); } } int main() { while(scanf("%d",&n)&&n){ ans=0; memset(b,0,sizeof(b)); for(int i=1;i<=n;i++){//編號我們從1開始,防止後面單點修改的時候死迴圈 scanf("%d",&a[i].value); a[i].index=i; } sort(a+1,a+n+1,cmp); for(int i=1;i<=n;i++){ ans+=sum(n)-sum(a[i].index); add(a[i].index); } printf("%lld\n",ans); } return 0; }

歸併排序:

用歸併排序也寫了一下,複習了一下歸併排序,開森。

歸併排序用了二分的思想,先把一個大區間不斷劃分成兩個小區間,知道不能劃分,然後回溯的時候進行區間合併,在我們合併的時候就可以順帶求出逆序對的數量了,額,如果不會歸併排序可以百度一下下。我們看下面的一個例子:

假設現在有了兩個較小的排好了序的區間分別是2 6 9和 3 5 10。根據歸併排序的原理,事實上他們在整個陣列上面是連續的,就是

2 6 9 3 5 10這樣排的,現在我們要和並他們,那麼首先我們把兩個區間裡面最小的數字拿出來比較一下,2<3,那麼2在第一個位置,然後6和3比較,6>3,那麼我們要把3放在6的前面,那麼我們要把3移幾位呢,在連續的2 6 9 3 5 10 裡面我們把3移到6前面是要經過6和9的,那麼就是要移兩位,那麼這六個數字就變成了2 3 6 9 5 10,同理,接下來6>5,我們還是要把5移到6前面,那麼還是2步,最後我們總結出一個規律,就是左邊區間的a[i]和右邊區間的a[j]相比較 ,如果a[i]<=a[j],那麼它們本來就是從小到大的,我們不移,如果是a[i]>a[j]那麼我們需要移的數目就是mid-i+1,事實上面就是左邊區間剩餘數字的數目。

程式碼:

#include<iostream>
#include<cstring>
#include<algorithm>
#include<queue>
#include<map>
#include<stack>
#include<cmath>
#include<vector>
#include<set>
#include<cstdio>
#include<string>
#include<deque> 
using namespace std;
typedef long long LL;
#define eps 1e-8
#define INF 0x3f3f3f3f
#define maxn 500005
LL ans,a[maxn],b[maxn],n;
void marge(int left,int mid,int right){
    int l=left;
    int r=mid+1;
    int cnt=l;
    while(l<=mid&&r<=right){
        if(a[l]<=a[r])
        b[cnt++]=a[l++];
        else{
            b[cnt++]=a[r++];
            ans+=mid-l+1;//在這裡加一下就可以了
        }
    }
    while(l<=mid) b[cnt++]=a[l++];
    while(r<=right) b[cnt++]=a[r++];
    for(int i=left;i<=right;i++)
    a[i]=b[i];
}
void marge_sort(int l,int r){
    if(l==r){
        return;
    }
    int mid=(l+r)/2;
    marge_sort(l,mid);
    marge_sort(mid+1,r);
    marge(l,mid,r);
}
int main()
{
    while(scanf("%lld",&n)&&n){
        ans=0;
        for(int i=0;i<n;i++){
            scanf("%lld",&a[i]);
        }
        marge_sort(0,n-1);
        printf("%lld\n",ans);
    }
    return 0;
}