POJ[2299]Ultra-QuickSort 逆序对:线段树||树状数组||分治

题目链接:http://poj.org/problem?id=2299

题目大意:给n个数( n50000 ),求逆序对数

求逆序对是一种经典题型,今天整理一下(闲的蛋疼)各种写法的逆序对…
复杂度都是 O(nlogn)

树状数组的思想就是对于每个数找出之前有k个数比他大,这个k就是对答案的贡献
可以用开个树状数组a[i]表示1~i有多少个数,则比i大的数一共有i-Sum(i)+1(后插入i)个数(也可以倒着开…)
优点:思维复杂度低 缺点:代码比较长,数据过大时要离散化,常数比分治(归并排序)略大


线段树的思想和树状数组差不多
记录答案只需要查询Sum(i+1,MAXN)即可…
优点:更好消磨时间 缺点:代码长,常数大,容易MLE,也要离散化


分治(归并排序)可能是最优的写法了吧…
和正常分治的思路一样,先处理左右区间(对他们从小到大排序)然后找出某时a[i]>a[j],则i的贡献就是mid-i+1
优点:代码短 常数小 缺点:递归层数深时容易崩溃..


不同算法的效率比较(从上至下依次为 线段树 树状数组 分治):
这里写图片描述

线段树代码:

#include<algorithm>
#include<cstring>
#include<ctype.h>
#include<cstdio>
using namespace std;
int n,x,tmp,maxn,cnt,top=1;
long long ans;
inline int max(int a,int b) {return a>b?a:b;}
int a[500020],b[500020];
struct Seg{
    int sum,l,r;
    Seg *ls,*rs;
}q[2000020];
inline int read(){
    char c=getchar();
    int x=0,f=1;
    while(!isdigit(c)) {if(c=='-') f=-1;c=getchar();}
    while(isdigit(c)) {x=(x<<3)+(x<<1)+c-'0';c=getchar();}
    return x*f;
}
bool cmp(int a,int b){return a<b;}
void maketree(int l,int r,Seg *k){
    k->l=l;k->r=r;
    if(l==r){
        k->sum=0;
        return;
    }
    k->ls=&q[++top];k->rs=&q[++top];
    int mid=(l+r)>>1;
    maketree(l,mid,k->ls);maketree(mid+1,r,k->rs);
    k->sum=k->ls->sum+k->rs->sum;
}
int Query_sum(int x,int y,Seg *k){
    if(k->l>=x && k->r<=y) return k->sum;
    int mid=(k->l+k->r)>>1;
    if(mid>=y) return Query_sum(x,y,k->ls);
    if(mid<x) return Query_sum(x,y,k->rs);
    return Query_sum(x,y,k->ls)+Query_sum(x,y,k->rs);
}
void add(int x,int v,Seg *k){
    if(k->l==k->r){
        k->sum=v;
        return;
    }
    int mid=(k->l+k->r)>>1;
    if(x<=mid) add(x,v,k->ls);
    else add(x,v,k->rs);
    k->sum=k->ls->sum+k->rs->sum;
}
main(){
    maketree(1,500020,&q[1]);
    while(""){
        for(int i=1;i<=2000000;i++) q[i].sum=0;
        ans=0;
        n=read();
        if(!n) break;
        for(int i=1;i<=n;i++)
            b[i]=a[i]=read();
        sort(b+1,b+n+1,cmp);
        cnt=unique(b+1,b+n+1)-b-1;
        for(int i=1;i<=n;i++){
            x=lower_bound(b+1,b+cnt+1,a[i])-b;
            ans=ans+Query_sum(x+1,500000,&q[1]);
            add(x,1,&q[1]);
        }
        printf("%lld\n",ans);
    }
return 0;
}


树状数组代码:

#include<algorithm>
#include<cstring>
#include<ctype.h>
#include<cstdio>
#define int long long
using namespace std;
int n,x,tmp,maxn,cnt;
long long ans;
inline int max(int a,int b) {return a>b?a:b;}
int s[500020],a[500020],b[500020];
inline int read(){
    char c=getchar();
    int x=0,f=1;
    while(!isdigit(c)) {if(c=='-') f=-1;c=getchar();}
    while(isdigit(c)) {x=(x<<3)+(x<<1)+c-'0';c=getchar();}
    return x*f;
}
inline int lowbit(int x){return x&-x;}
inline int sum(int x){
    for(tmp=0;x;x=x-(x&-x)) tmp=tmp+s[x];
    return tmp;
}
inline int add(int x,int v){
    for(;x<=500000;x=x+(x&-x)) s[x]=s[x]+v;
}
bool cmp(int a,int b){return a<b;}
main(){
    while(""){
        ans=0;
        n=read();
        if(!n) break;
        memset(s,0,sizeof s);
        for(int i=1;i<=n;i++)
            b[i]=a[i]=read();
        sort(b+1,b+n+1,cmp);
        cnt=unique(b+1,b+n+1)-b-1;
        for(int i=1;i<=n;i++){
            x=lower_bound(b+1,b+cnt+1,a[i])-b;
            ans=ans+i-sum(x)-1;
            add(x,1);
        }
        printf("%lld\n",ans);
    }
return 0;
}


归并排序算法:

#include<algorithm>
#include<cstdio>
#define int long long
using namespace std;
int n,ans;
int a[500020],s[500020];
void calc(int l,int r){
    if(l==r) return;
    int mid=(l+r)>>1,p=l,q=mid+1,top=l;
    calc(l,mid);calc(mid+1,r);
    while(p<=mid||q<=r){
        if(q>r||(p<=mid&&a[p]<=a[q]))s[top++]=a[p++];
        else {s[top++]=a[q++];ans=ans+mid+1-p;}
    }
    for(int i=l;i<=r;i++) a[i]=s[i];
}
main(){
    while(""){
        ans=0;
        scanf("%lld",&n);
        if(!n) break;
        for(int i=1;i<=n;i++)
            scanf("%lld",a+i);
        calc(1,n);
        printf("%lld\n",ans);
    }
return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值