歸併排序求逆序對--分治(洛谷P1908)

題目鏈接https://www.luogu.org/problem/P1908

題目描述

貓貓TOM和小老鼠JERRY最近又較量上了,但是畢竟都是成年人,他們已經不喜歡再玩那種你追我趕的遊戲,現在他們喜歡玩統計。最近,TOM老貓查閱到一個人類稱之爲“逆序對”的東西,這東西是這樣定義的:對於給定的一段正整數序列,逆序對就是序列中ai>aj且i<j的有序對。知道這概念後,他們就比賽誰先算出給定的一段正整數序列中逆序對的數目。
Update:數據已加強。

輸入格式

第一行,一個數n,表示序列中有n個數。

第二行n個數,表示給定的序列。序列中每個數字不超過10^9109

輸出格式

給定序列中逆序對的數目。

輸入輸出樣例

輸入 

6
5 4 2 6 3 1

輸出

11

說明/提示

對於25%的數據,n≤2500

對於50%的數據,n≤4×10^4。

對於所有數據,n≤5×10^5

請使用較快的輸入輸出

應該不會n方過50萬吧 by chen_zhe


簡單講一下吧,歸併排序就是將區間[l,r]分成[l,mid]和[mid+1,r]然後用另外一個數組將他們有序地記錄下來即一直記錄最小值就好了,接着我們把這個有序的數組賦值給它,然後它再傳遞給上一層。由於[l,mid]是有序的,那麼如果在[mid+1,r]中找到小於前面區間的數的話,逆序對的個數就是mid-p+1(其中p是[l,mid]中的一個位置):

int ll=l,rr=mid+1;
while (ll<=mid && rr<=r) {
	if (a[ll]<=a[rr]) {
		b[++cnt]=a[ll];
		ll++;
	} 
	else {
		ans+=(mid-ll+1);
		b[++cnt]=a[rr];
		rr++;
	}
}

很明顯的是歸併排序的常數比線段樹小得多,跑到非常快。

以下是AC代碼:

#include <bits/stdc++.h>
using namespace std;

const int mac=5e5+10;

int a[mac],b[mac];
long long ans=0;

void cdq(int l,int r)
{
    if (l==r) return;
    int mid=(l+r)>>1;
    cdq(l,mid);cdq(mid+1,r);
    int ll=l,rr=mid+1;
    for (int i=l; i<=r; i++) b[i]=a[i];
    int cnt=0;
    while (ll<=mid && rr<=r){
        if (a[ll]<=a[rr]) {
            b[++cnt]=a[ll];
            ll++;
        }
        else {
            ans+=(mid-ll+1);
            b[++cnt]=a[rr];
            rr++;
        }
    }
    while (ll<=mid) b[++cnt]=a[ll++];
    while (rr<=r) b[++cnt]=a[rr++];
    cnt=0;
    for (int i=l; i<=r; i++) a[i]=b[++cnt];
}

void in(int &x)
{
    char ch=getchar();
    int f=0;
    while (ch>'9' || ch<'0') ch=getchar();
    while (ch<='9' && ch>='0') f=(f<<3)+(f<<1)+ch-'0',ch=getchar();
    x=f;
}

void out(long long x)
{
    if (x>=10)
        out(x/10);
    putchar(x%10+'0');
}

int main()
{
    int n;
    in(n);
    for (int i=1; i<=n; i++) 
        in(a[i]);
    cdq(1,n);
    out(ans);
    putchar('\n');
    return 0;
}

 

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