POJ2299(树状数组)

求逆序数对。

看了一下别人的题解,发现这题还可以用树状数组的方法来做。

因为数据大小范围为0~999,999,999,若用此数作为数组下标肯定会超出内存限制,所以采用离散化的方法先将数据范围缩小。

比如9,1,4,3,5可以变为5,1,3,2,4来处理,这样数组最大的下标就是5,大大缩小了数据范围,因为n<500,000,所以数组开500,000就够了。

思路是从头到尾循环一次,假设循环变量为pos,即当前位置为pos,则对C[i]求和表示1~pos的范围内存在多少个比i小的数,而题目要求是逆序数对,则用对C[n]求和减去对C[i]求和即可表示i前面存在了多少个比它大的数,以上面的5,1,3,2,4为例子:

pos为1时,num[pos]=5,getsum(C[num[pos]])=0,getsum(C[n])=0,getsum(C[n])-getsum(C[num[pos]])=0,

pos为2时,num[pos]=1,getsum(C[num[pos]])=0,getsum(C[n])=1,getsum(C[n])-getsum(C[num[pos]])=1,

pos为3时,num[pos]=3,getsum(C[num[pos]])=1,getsum(C[n])=2,getsum(C[n])-getsum(C[num[pos]])=1,

pos为4时,num[pos]=2,getsum(C[num[pos]])=1,getsum(C[n])=3,getsum(C[n])-getsum(C[num[pos]])=2,

pos为5时,num[pos]=4,getsum(C[num[pos]])=3,getsum(C[n])=4,getsum(C[n])-getsum(C[num[pos]])=1,

答案即为0+1+1+2+1=5,可验证一下逆序数对为(5,1),(5,3),(5,2),(3,2),(5,4)共五对。

树状数组的求解过程:


如果使用同样的方法而不用树状数组的话,用C[i]表示1~pos的范围内存在多少个比i小的数,虽然求和效率高,但是增加元素时效率很低,比如说,在进行到pos=2的时候,需要修改的元素有C[2],C[3],……,C[n],效率极低。用树状数组虽然求和效率低,但是增加元素时效率高,合起来的效率也高很多,因而用树状数组更好。


不用树状数组的数组变化:(同样是5,1,3,2,4的例子)

C[1]=0,C[2]=0,C[3]=0,C[4]=0,C[5]=0,    5~5有0对

C[1]=0,C[2]=0,C[3]=0,C[4]=0,C[5]=1,    5~1有C[5]-C[1]=1对

C[1]=1,C[2]=1,C[3]=1,C[4]=1,C[5]=2,    5~3有C[5]-C[3]=1对

C[1]=1,C[2]=1,C[3]=2,C[4]=2,C[5]=3,    5~2有C[5]-C[2]=2对

C[1]=1,C[2]=2,C[3]=3,C[4]=3,C[5]=4,    5~4有C[5]-C[4]=1对

C[1]=1,C[2]=2,C[3]=3,C[4]=4,C[5]=5,    (这一组数据实际上没有用到)

0+1+1+2+1=5对


附上转自http://hi.baidu.com/lewutian/item/495088a9bb2d0e3f020a4d3c的代码:(与上面描述的变量名等可能有所不同)

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
#define M 500005
int cc[M],n,num[M];
struct D
{
int val,no;
}data[M];
bool cmp(D a,D b)
{
return a.val<b.val;
}
int lowbit(int x)
{
    return x&(-x);
}
void up(int x,int nu)
{
    while(x<=n)
    {
        cc[x]+=nu;
        x+=lowbit(x);
    }
}
int getsum(int x)
{
    int sum=0;
    while(x)
    {
        sum+=cc[x];
        x-=lowbit(x);
    }
    return sum;
}
int main()
{
    int i;
    long long ans;
        while(scanf("%d",&n)&&n)
        {
            memset(cc,0,sizeof(cc));
            for(i=0;i<n;i++)
            {
                scanf("%d",&data[i].val);
                data[i].no=i;
            }
       sort(data,data+n,cmp);
       for(i=0;i<n;i++)
        num[data[i].no]=i+1;
            for(i=0,ans=0;i<n;i++)
            {
                ans+=(getsum(n)-getsum(num[i]));
                up(num[i],1);
            }
            printf("%lld\n",ans);
        }
    }


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值