1. 程式人生 > >逆序對總結 【各種求法】

逆序對總結 【各種求法】

原文連結
.

逆序對:設 A 為一個有 n 個數字的有序集 (n>1),其中所有數字各不相同。如果存在正整數 i, j 使得 1 ≤ i < j ≤ n 而且 A[i] > A[j],則 A[i], A[j]> 這個有序對稱為 A 的一個逆序對,也稱作逆序數。

Example :求給出一個由n個數組成的序列,求出該序列 的逆序對總數目
暴力解法這裡就不提了。

思路 歸併排序:(即使序列存在相同元素,該演算法也適用,且程式碼不用修改)
歸併排序是將數列a[l,h]分成兩半a[l,mid]和a[mid+1,h]分別進行歸併排序,然後再將這兩半合併起來。在合併的過程中(設l<=i<=mid ,mid+1<=j<=h),
當a[i]<=a[j]時,並不產生逆序數;當a[i]>a[j]時,在前半部分中比a[i]大的數都比a[j]大,將a[j]放在a[i]前面的話,逆序數要加上mid+1-i。因此,可以在歸併
排序中的合併過程中計算逆序數.

程式碼 實現一 歸併排序

#include<bits/stdc++.h>
#define LL long long
using namespace std;
const int MAXN = 50000+5;
const int MAXM = 1e5;
int a[MAXN],temp[MAXN];
LL ans=0;
int n;
void merge(int le,int mid,int ri){//使每兩部分【都已經分別有序】,合併為一個有序集合 
    int i,j,k;
    // le到mid 是一個有序部分,mid+1到ri是一個有序部分 ,合併就行了  
    i=le;j=mid+1
;k=le; for(;i<=mid&&j<=ri;){ if(a[i]>a[j]){ temp[k++]=a[j++]; ans+=mid-i+1; }else temp[k++]=a[i++]; } while(i<=mid) temp[k++]=a[i++]; while(j<=ri) temp[k++]=a[j++]; for(i=le;i<=ri;i++) a[i]=temp[i]; } void merge_sort(int
le,int ri){//不斷的分為一半,來使各個部分非遞減有序 if(le<ri){ int mid=(le+ri)>>1; merge_sort(le,mid); merge_sort(mid+1,ri); merge(le,mid,ri); } } int main(){ while(~scanf("%d",&n)){ for(int i=0;i<n;i++) scanf("%d",&a[i]); ans=0; merge_sort(0,n-1); printf("%lld\n",ans); } return 0; }

思路 二叉樹:(我們先說序列不存在相同元素的情況,存在相同元素的情況後面補充)
1,初始化所有節點對應區間為0;
2,優先插入最大值,我們假設當前插入位置為pos,更新pos對應的區間為1。在每次插入後統計在pos位置之前有多少個數,說白了就是求 1到pos-1 區間 的數值總和;
3,累加每次插入的統計值,即是所求的當前序列的逆序對總數目。
這裡寫圖片描述

struct record
{
    int val;//記錄數值  
    int pos;//記錄該數值在序列的位置 
}num[];
bool cmp(record a,record b)//優先數值大的 
{
    return a.val > b.val;
}

程式碼實現二–>線段樹:
我們可以從大到小排序原序列,然後依次插入線段樹的相應位置,每插入一次,判斷一下它前面有幾個數,這時候逆序數就加相應個數。
【線段樹可以理解將每一個點作為值1,在判斷一個數前面有幾個數的時候,我們求一下從節點1到當前節點前一個,的總和就代表前面有幾個數。】

#include<bits/stdc++.h>
using namespace std;
#define LL long long
#define ll o<<1
#define rr o<<1|1
#define lson o<<1,le,mid
#define rson o<<1|1,mid+1,ri
const int MAXN =50000+10;
const int MAXM = 1e6;
const LL mod = 9973;
struct Tree{
    int l,r;
    int sum;
}tree[MAXN<<2];
int n;
struct Node{
    int id,val;
}node[MAXN];
bool cmp(Node a,Node b){
    return a.val>b.val;
}
void pushup(int o){
    tree[o].sum=tree[ll].sum+tree[rr].sum;
}
void build(int o,int le,int ri){
    tree[o]={le,ri,0};
    if(le==ri)  return ;
    int mid=(le+ri)>>1;
    build(lson);
    build(rson);
    pushup(o);
}
void update(int o,int pos,int val){
    if(tree[o].l==tree[o].r&&tree[o].l==pos){
        tree[o].sum+=val;
        return ;
    }
    int mid=(tree[o].l+tree[o].r)>>1;
    if(pos>mid) update(rr,pos,val);
    else if(pos<=mid) update(ll,pos,val);
    pushup(o);
}
int query(int o,int le,int ri){
    if(tree[o].l>=le&&tree[o].r<=ri) return tree[o].sum;
    int mid=(tree[o].l+tree[o].r)>>1;
    if(le>mid) query(rr,le,ri);
    else if(ri<=mid) query(ll,le,ri);
    else return  query(ll,le,mid)+query(rr,mid+1,ri);
}
int main(){
     int n,i;
     LL ans;
    while(scanf("%d",&n)!=EOF){
        build(1,1,n);
        for(int i=0;i<n;i++){
            scanf("%d",&node[i].val);
            node[i].id=i+1;
        }
        sort(node,node+n,cmp);
        ans=0;
        for(int i=0;i<n;i++){
            update(1,node[i].id,1);
            if(node[i].id==1)  continue;
            ans+=query(1,1,node[i].id-1);
        }
        printf("%lld\n",ans);
    }
    return 0;
}

程式碼實現三 –>樹狀陣列:

#include <cstdio>
#include <cstring>
#include <algorithm>
#define MAX 500000
#define LL long long 
using namespace std;
int c[MAX];
int n;
struct record
{
    int val, pos;//記錄數值 和 位置 
}num[MAX];
bool cmp(record a,record b)
{
    return a.val > b.val;
}
int lowbit(int x)
{
    return x&(-x);
}
int sum(int x)//求和 
{
    int s = 0;
    while(x > 0)
    {
        s += c[x];
        x -= lowbit(x);
    }
    return s;
}
void update(int x)//更新 
{
    while(x <= n)
    {
        c[x] += 1;
        x += lowbit(x); 
    }
}
int main()
{
    int i, j;
    LL ans;//統計總數目 
    while(scanf("%d", &n), n)
    {
        memset(c, 0, sizeof(c));//初始化 
        for(i = 0;i < n; i++)
        {
            scanf("%d", &num[i].val);
            num[i].pos = i + 1;
        }
        sort(num, num+n, cmp);
        ans = 0;
        for(i = 0;i < n; ++i)//每一次插入當前最大值 
        {
            update(num[i].pos);
            ans += sum(num[i].pos-1);//統計前面有多少個數 
        }
        printf("%lld\n", ans);
    }
    return 0;
}



補充存在相同元素情況:
上面程式碼實現的是序列不存在相同元素的情況,若是存在相同元素,只需限制一下插入順序就ok了。
修改程式碼如下(其餘程式碼不用修改):

struct record
{
    int val;
    int pos;
}num[];
bool cmp(record a, record b)
{
    if(a.val != b.val)
    return a.val > b.val;
    else
    return a.pos > b.pos;
}