题目链接:http://poj.org/problem?id=2299
做法一:归并排序求逆序对
归并排序求逆序对的做法很经典,在归并排序中,如果 left<=i<=mid 和 mid+1 <=j<=right,(i<j) ,那么,如果 a[i]<=a[j],这并不产生逆序对,但是,如果a[i]>a[j],如果在mid之前有大于k个大于a[j]的数,那么ans+=k,k是多少呢?k=mid-i+1!为什么?你想想,因为a[i]>a[j],因为 i~~mid已经排序好了,就是说a[i]<a[i+1]<a[i+2]<....<a[mid],就是说i~mid的数都可以与j形成逆序对,证明完毕,k=mid-i+1。在每次合并的时候 ,如果a[i]>a[j],ans+=k即可。
贴上渣渣代码:
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <stack>
#include <queue>
#include <cstring>
#include <map>
#include <set>
#define LL long long
#define pb push_back
#define pf push_front
#define loop(a,b,c) for(int a=b;a<=c;a++)
#define rloop(a,b,c) for(int a=b;a>=c;a--)
#define clr(a,b) memset(a,b,sizeof a)
#define inf 1<<30
#define x first
#define y second
#define maxn 500005
using namespace std;
int a[maxn],n;
LL ans;
void merge(int l,int r)
{
int *b = new int[maxn];
int m = (l+r)/2,i,j,len=0;
i=l;
j=m+1;
while(i<=m&&j<=r)
{
if(a[i]<=a[j]) b[len++] = a[i++];
else b[len++] = a[j++],ans+=m-i+1;
}
while(i<=m) b[len++] = a[i++];
while(j<=r) b[len++] = a[j++];
loop(i,0,len-1) a[l+i] = b[i];
delete []b;
}
void merge_sort(int l,int r)
{
if(l>=r) return;
int m = (l+r)/2;
merge_sort(l,m);
merge_sort(m+1,r);
merge(l,r);
}
void solve()
{
loop(i,1,n)
scanf("%d",&a[i]);
ans=0;
merge_sort(1,n);
printf("%lld\n",ans);
}
int main()
{
//freopen("data.txt","r",stdin);
while(scanf("%d",&n)&&n)
solve();
}
额,跑了1069MS
做法二:树状数组
当我听说可以用树状数组做的时候,确实有点摸不着头脑。。。完全没想到可以用树状数组去做(怪我渣咯)
怎么做呢?其实原理很简单!就取样例来说吧!
对于样例 n=5 ,a[1~n] = {9,1,0,5,4},不妨设d[j]表示 j 是否出现过,若是,标记为1
当i=1,d[9]=1,对0~9求和,即sum(9)=1,说明了有多少个数是不大于9的,那么i-sum(a[i])就表示有多少个数比a[i] 大了,在这里就是1-sum(9)=0
当i=2,d[1]=1,对0~1求和,sum(1)=1,那么在i=0~2就有2-sum(1)=1个数比2大
当i=3,d[0]=1,对0~0求和,sum(0)=1,那么在i=0~3就有3-sum(0)=2个数比1大
以此类推。。。
将上面的i-sum(a[i])加起来,就是答案了
但是,题目所给的数范围是0~999,999,999,根本没办法开一个d数组那么大!但是,观察到n最大才500,000,其实每个a[i]都可以映射成1~~500,000的每个数!
简而言之,就是要对数组进行离散化!
还是对于样例n=5 ,a[1~n] = {9,1,0,5,4},我们用一个结构体(或者pair)来记录原输入的值大小以及它的下标,对于样例而言,就是
{(x,y)|(9,1),(1,2),(0,3),(5,4),(4,5),x是输入的值,y是下标}
对上面按x值大小排序得
{(x,y)|(0,3),(1,2),(4,5),(5,4),(9,1),x是输入的值,y是下标}
用一个b数组来存储离散化后的数组,即有b[x[i]]=i !!结果为 {5,2,1,4,3}
为什么要这样做呢?一开始我也有点懵逼,毕竟我是第一次接触离散化这个东西。
但是,只要认真想想就知道, 其实就是改变了x的值,把x的范围缩减到1~500,000的范围,然后变回原来的顺序而已。可以看出离散化后,数组中所有元素的大小关系是完全没有变化的!
然后我们就可以开一个足够大的数组去实现之前的算法了!
贴上代码:
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <stack>
#include <queue>
#include <cstring>
#include <map>
#include <set>
#define LL long long
#define pb push_back
#define pf push_front
#define loop(a,b,c) for(int a=b;a<=c;a++)
#define rloop(a,b,c) for(int a=b;a>=c;a--)
#define clr(a,b) memset(a,b,sizeof a)
#define inf 1<<30
#define x first
#define y second
#define maxn 500005
using namespace std;
typedef pair<int,int> P;
int n,ans,c[maxn],d[maxn];
P a[maxn];
int lowbit(int x) {return x&(-x);}
void updata(int i,int x)
{
while(i<=n)
{
c[i]+=x;
i += lowbit(i);
}
}
int sum(int i)
{
int s = 0;
while(i>0)
{
s += c[i];
i -= lowbit(i);
}
return s;
}
void solve()
{
loop(i,1,n)
scanf("%d",&a[i].x),a[i].y=i;
ans = 0;
sort(a[i]+1,a[i]+n+1);
loop(i,1,n) d[i]=a[i].y;
// loop(i,1,n) printf("%d\n",d[i]);
loop(i,1,n)
{
updata(d[i],1);
ans+=i-sum(d[i]);
}
printf("%d\n",ans);
}
int main()
{
//freopen("data.txt","r",stdin);
while(scanf("%d",&n)&&n)
solve();
}
运行了422MS,按道理说呢时间复杂度都是nlogn的,不过实际运行起来,相差还是蛮大的,难道跟归并排序的递归有关?