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;
}