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