POJ 2299 Ultra-QuickSort (树状数组、归并排序)

来源:互联网 发布:农村淘宝 app 下载 编辑:程序博客网 时间:2024/06/18 09:03

题目链接: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 500005using 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 500005using 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的,不过实际运行起来,相差还是蛮大的,难道跟归并排序的递归有关?

0 0
原创粉丝点击