POJ 2299 Ultra-QuickSort (初学树状数组)

来源:互联网 发布:linux命令hexdump 编辑:程序博客网 时间:2024/05/20 20:45

才学的树状数组,按往常一样附上几个好的资料

http://www.topcoder.com/tc?module=Static&d1=tutorials&d2=binaryIndexedTrees

这是czyuan神的OJ习题总结:http://hi.baidu.com/czyuan_acm/item/915764070c200393a3df43db




题目大意:

给一些(n个)乱序的数让你求冒泡排序需要交换数的次数(n<=500000)数据范围是 0 ≤ a[i] ≤ 999,999,999


所以先要离散化,然后用合适的数据结果求出逆序

可以用线段树一步一步添加a[i],每添加前查询前面添加比它的大的有多少个就可以了。


方法一:

也可用树状数组,由于树状数组求的是(1...x)的数量和所以每次添加前查询i-sum(a[i])即可

//7384K500MS#include<cstdio>#include<algorithm>#include<iostream>#include<cstring>using namespace std;#define ll long long#define lowbit(x) (x&-x)const int M=5e5+100;struct node{    int index;    int val;}num[M];int Hash[M];int tree[M]; //树状数组int n;bool cmp(const node&a,const node&b ){    return a.val<b.val;}void add(int rt,int x){    while(rt<=M-1){        tree[rt]+=x;        rt+=lowbit(rt);    }}ll getsum(int rt){    ll s=0;    while(rt>0){        s+=tree[rt];        rt-=lowbit(rt);    }    return s;}int main(){    while(scanf("%d",&n),n){        memset(tree,0,sizeof(tree));        ll ans=0;        for(int i=1;i<=n;i++){            scanf("%d",&num[i].val);            num[i].index=i;        }        sort(num+1,num+1+n,cmp);        int l=0;        Hash[num[1].index ]=++l;        for(int i=2;i<=n;i++){            if(num[i].val!=num[i-1].val) l++;            Hash[num[i].index ]=l;        }        for(int i=1;i<=n;i++){            int val=Hash[i];            ans+=(ll)(i-1)-getsum(val);            add(val,1);        }        printf("%I64d\n",ans);    }    return 0;}

方法二:

由于离散化还是有点繁杂,可以做些手脚,根本不需要用Hash[i]数组把离散后的数列记录下来,可以直接把原数列排序后,二分出对应的序号,这个时候数列中两个相同的值也有不同的序号,但是对于求逆序对完全没影响(取决去sort的两数相等时的排序方式)

用这种方法编程就一些了:


//5620K688MS#include<cstdio>#include<algorithm>#include<iostream>#include<cstring>using namespace std;#define ll long long#define lowbit(x) (x&-x)const int M=5e5+100;int num[M];int Hash[M];int tree[M]; //树状数组int n;int Bin(int val) //二分出离散后的序号{    int l=0,r=n-1;    while(r>=l){        int m=(l+r)>>1;        if(Hash[m]==val) return m+1;        if(Hash[m]>val) r = m-1;        else l=m+1;    }}void add(int rt,int x){    while(rt<=M-1){        tree[rt]+=x;        rt+=lowbit(rt);    }}ll getsum(int rt){    ll s=0;    while(rt>0){        s+=tree[rt];        rt-=lowbit(rt);    }    return s;}int main(){    while(scanf("%d",&n),n){        memset(tree,0,sizeof(tree));        ll ans=0;        for(int i=0;i<n;i++){            scanf("%d",&num[i]);            Hash[i]=num[i];        }        sort(Hash,Hash+n);        for(int i=0;i<n;i++){            int val=Bin(num[i]);            ans+=(ll)i-getsum(val);            add(val,1);        }        printf("%I64d\n",ans);    }    return 0;}

方法三:

由于要求逆序对数,就是在建树的时候输入某个数,求出在这个数输入前输入了几个比它更大的数,这个时候离散化可以直接逆序编号,也简单一些:

//7384K516MS#include<cstdio>#include<algorithm>#include<iostream>#include<cstring>using namespace std;#define ll long long#define lowbit(x) (x&-x)const int M=5e5+100;struct node{    int index;    int val;}num[M];int Hash[M];int tree[M]; //树状数组int n;bool cmp(const node&a,const node&b ){    return a.val>b.val;//逆序编号}void add(int rt,int x){    while(rt<=M-1){        tree[rt]+=x;        rt+=lowbit(rt);    }}ll getsum(int rt){    ll s=0;    while(rt>0){        s+=tree[rt];        rt-=lowbit(rt);    }    return s;}int main(){    while(scanf("%d",&n),n){        memset(tree,0,sizeof(tree));        ll ans=0;        for(int i=1;i<=n;i++){            scanf("%d",&num[i].val);            num[i].index=i;        }        sort(num+1,num+1+n,cmp);        int l=0;        Hash[num[1].index ]=++l;        for(int i=2;i<=n;i++){            if(num[i].val!=num[i-1].val) l++;            Hash[num[i].index ]=l;        }        for(int i=1;i<=n;i++){            int val=Hash[i];            ans+=getsum(val-1);            add(val,1);        }        printf("%I64d\n",ans);    }    return 0;}


方法四:

还有czyuan神提供的一个方法,他认为sum和add函数是通用的

其实就是两个访问不同路径的函数:

(1, …x-Lowbit(x-Lowbit(x))), x-Lowbit(x), x)  x递增路径

(x, x+Lowbit(x), x+Lowbit(x+Lowbit(x))),…) x递减路径

我们既可以修改x增大的路,求和x减小的路;也可以修改x减小的路,求和x增大的路,根据题目的需要来决定用哪种。

他的总结:

总结:我们可以发现其实Update()和Getsum()这两个函数是相同的,我们可以用Up()和Down()来代替它们。Up()为操作x递增的路径,Down()为操作x递减的路径。       

Up()和Down() 有四种组合 : 


       1. Up()表示修改单点的值,Down()表示求区间和。        

       2. Down()表示修改单点的值,Up()表示求区间和。     

       3. Up()表示修改区间,Down()表示求单点的值。        

       4. Down()表示修改区间,Up()表示求单点的值。 
       

1和2根据求比它大还是比它小来选择,而3和4种适用条件则相同,用其中一种即可。


代码:

//7384K516MS#include<cstdio>#include<algorithm>#include<iostream>#include<cstring>using namespace std;#define ll long long#define lowbit(x) (x&-x)const int M=5e5+100;struct node{    int index;    int val;}num[M];int Hash[M];int tree[M]; //树状数组int n;bool cmp(const node&a,const node&b ){    return a.val<b.val;}void add(int rt,int x)  //rt递减路更新{    while(rt>0){        tree[rt]+=x;        rt-=lowbit(rt);    }}ll getsum(int rt) //rt递增路求和{    ll s=0;    while(rt<=M-1){        s+=tree[rt];        rt+=lowbit(rt);    }    return s;}int main(){    while(scanf("%d",&n),n){        memset(tree,0,sizeof(tree));        ll ans=0;        for(int i=1;i<=n;i++){            scanf("%d",&num[i].val);            num[i].index=i;        }        sort(num+1,num+1+n,cmp);        int l=0;        Hash[num[1].index ]=++l;        for(int i=2;i<=n;i++){            if(num[i].val!=num[i-1].val) l++;            Hash[num[i].index ]=l;        }        for(int i=1;i<=n;i++){            int val=Hash[i];            ans+=getsum(val+1);            add(val,1);        }        printf("%I64d\n",ans);    }    return 0;}

按不同的路径也可以实现sum和add的功能,理由不太容易表述,有点创造性,可以自己画画图理解理解

 

方法五:

用分治法求逆序数也可,分成两个数组B,C  。  B中的逆序数+C中的逆序数+对于每一个C[i]B中比它大的个数

所以可以借助归并排序实现顺带算出逆序数

//5428K625MS#include<cstdio>#include<algorithm>#include<iostream>#include<cstring>using namespace std;#define ll long long#define lowbit(x) (x&-x)const int M=5e5+100;int num[M];int tmp[M];int Hash[M];int n;int Bin(int val) //二分出离散化后的序号{    int l=0,r=n-1;    while(r>=l){        int m=(l+r)>>1;        if(Hash[m]==val) return m+1;        if(Hash[m]>val) r = m-1;        else l=m+1;    }}ll merge_count(int l,int r){    if(l==r){        return 0;    }    ll cnt=0;    int m=(l+r)>>1;    cnt+=merge_count(l,m);    cnt+=merge_count(m+1,r);    int a=0,b=l,c=m+1;    while(a<r-l+1){        if(b<=m&& (c==r+1 ||num[b]<=num[c])){            tmp[a++]=num[b++]; // sort        }        else{            tmp[a++]=num[c++];  // sort            cnt+=m-b+1;        }    }    for(int i=0;i<r-l+1;i++) //还原到原数列        num[l+i]=tmp[i];    return cnt;}int main(){    while(scanf("%d",&n),n){        for(int i=0;i<n;i++){            scanf("%d",&num[i]);            Hash[i]=num[i];        }        sort(Hash,Hash+n);        for(int i=n-1;i>=0;i--){            int val=Bin(num[i]);            num[i+1]=val;            //我离散后的数列设成从1开始到n,因为后面分治怕出错就套用了线段树从结点1开始的写法...2333        }                printf("%I64d\n",merge_count(1,n));    }    return 0;}


0 0
原创粉丝点击