poj1990 树状数组+排序

/**
 * poj1990 树状数组+排序
 * 这个是个稍稍复杂的题目,涉及到了树状数组的使用.如果直接一个一个成对做判断的话,复杂度在n*(n-1)/2量级上,肯定完不成
 * 我们先把所有牛的叫声v按升序排列,那么如果从最左边往右计算到当前牛i,v就是用c[i].v就可以了
 * 再看x,我们将牛i左边的牛分成位置(x)在牛i之前和之后两种,分别计算
 * 如果我们知道位置上在牛i之前的牛的个数为count,这些牛相对于位置0的总位置和为dis,那么这些叫声小于牛i,位置也小于牛i的牛,与牛i之间的总叫声就是c[i].v*(count*c[i].x - dis)
 * 再看位置上在牛i之后的牛,这些叫声小于牛i,但位置大于牛i的牛,与牛i之间的总叫声就是c[i].v*(discount - dis - (i-1-count)*c[i].x),
 * 其中,discount表示所有之前统计过的牛相对于0的距离和,减去dis就是叫声小于i,但位置大于i的牛的距离和,i就是牛i的编号,以1开始,如果以0开始,就不需要-1了
 * discount累加就可以了,那么对于count和dis的统计,就需要使用树状数组来实现。
 * 如果知道树状数组还没有思路,那可能是没有注意到,我们这里是在使用距离x作为树状数组的索引,而大量没有记录的值,都是默认的0,而0不会对累加和的计算造成影响
 * 树状数组的基本原理,我打算在更纯的题目里再巩固一下,这里大概说一下。它的意思是,构造一个树形的结构,每一个母节点统计了下面一定个数的叶子点的和,因此在更新叶子的值和进行求和计算时,复杂度都是log量级
 * 因此树状数组非常适用于计算频繁修改的,或是动态的一个数组的前若干个数的总和
 * 树状数组有一个很经典的图,一查就查到如果叶子点,即需要累加的数组是A,累加的结果(非叶子点)是C
 * 1      10        11     100             101    110       111    1000
 * C1=A1, C2=A1+A2, C3=A3, C4=A1+A2+A3+A4, C5=A5, C6=A5+A6, C7=A7, C8=A1+A2+A3+A4+A5+A6+A7+A8
 * 稍作总结可以看出 Cn=A(n-2^k+1) + ...An.其中k为n二进制末尾0的个数,简单地说,末尾有k个0,那么就从An向前加2^k个数
 * 2^k的计算方法有一个比较快捷的算法即x&(x^(x-1))
 * 要计算累加和时,只需要依次累加Cn,再令n=n-lowbit(n),即将n的最右边的1置为0,直到n变为0,累加和即为A1+...An
 * 比如6=0b'110 A1+..A6=C6+C4
 * 要进行修改时,比如对Ai进行+x修改时,也只需要从i出发,每次将Ci+=x,然后令i=i+lowbit(i),直到i超过树状数组最大索引,就实现了在包含Ai的Cj的修改
 * 比如修改C1,只需修改1,10,100,1000即可
 * 容易看出,累加和修改的复杂度都是log(n)级别
 */

#include <cstdio>
#include <iostream>
#include <cstdlib>
#include <cstring>
using namespace std;
const int MAX_NUM = 20001;

struct cow{
    int v;
    int x;
} c[MAX_NUM];
long long tcount[MAX_NUM];
long long tdis[MAX_NUM];


//按v升序排列,v相等的按x升序排列
int cmp(const void* a,const void* b){
    cow *p1 = (cow*)a, *p2 = (cow*)b;
    if(p1->v != p2->v)
        return p1->v - p2->v;
    else
        return p1->x - p2->x;
}

inline int lowbit(int x){
    return x&(x^(x-1));
}

//更新树状数组
void add(long long arr[],int i,int a){
    while(i<=MAX_NUM){
        arr[i]+=a;
        i+=lowbit(i);//从小到大逐个更新受影响的值
    }
}

//通过树状数组求和
long long sum(long long arr[],int i){
    long long res=0;
    while(i>0){
        res+=arr[i];
        i-=lowbit(i);//从小到大逐个累加受影响的值
    }
    return res;
}

int main(){
    int n;
    long long alldis = 0;
    scanf("%d",&n);
    for(int i=0;i<n;++i){
        scanf("%d%d",&c[i].v,&c[i].x);
    }    
    qsort(c,n,sizeof(cow),cmp);
    memset(tcount,0,sizeof(tcount));
    memset(tdis,0,sizeof(tdis));

    long long res = 0;
    for(int i=0;i<n;++i){
        long long a = sum(tcount,c[i].x),b = sum(tdis,c[i].x);
        res += c[i].v * (a*c[i].x - b);
        res += c[i].v * (alldis - b -(i-a)*c[i].x);
        add(tcount,c[i].x,1);
        add(tdis,c[i].x,c[i].x);
        alldis += c[i].x;
    }

    printf("%lld\n",res);
    return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值