POJ 1990 MooFest

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


题意:有n个奶牛站成一排,每只奶牛都有两个值,v和pos,如果两只奶牛i和j想传话,需要消耗max(v[i],v[j]) * | pos[i]-pos[j] |
现在求n个奶牛之间互相都传话一次,一共需要消耗多少?


思思路:答案是n*(n-1)/2个数的和,但是如果我们一个个求的话,会超时。
首先,我们规定每只奶牛贡献的答案是,它与比它v值小的奶牛所对话产生的值,这样的话可以让v值固定,而且也可以把所有情况考虑进去。每只奶牛所产生的答案变成v*(∑|pos-pos[j]|)v[j]<=v ,如果把所有的j分为左右两侧计算的话,把符合的奶牛分成左边和右边,那么就可以去掉绝对值,变成 v*( n*pos - ∑pos[j'] )+ v*( ∑pos[j'']-n*pos )
这样我们就可以用树状数组去优化这个求和。
根据公式,我们需要求距离和还有符合个数,所以我们用两个树状数组sum,num分别维护。
先统计左侧的答案:我们每次遇到一只奶牛i,那么它的左侧贡献的答案为:v[i]*( query(v[i],num)*pos[i] - query(v[i],sum) ), 累加完答案后更新树状数组。
同理,再统计右侧的答案。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <algorithm>


using namespace std;

#define rep(i,j,k) for(int i = j; i <= k; i++ )
#define Rrep(i,j,k) for(int i = j; i >= k; i-- )
#define Clean(x,y) memset(x,y,sizeof(x))
#define LL long long

const int maxn  =200000;

int n;

struct node
{
    int pos;
    int v;
}a[maxn+10];
bool cmp(node a,node b)
{
    return a.pos < b.pos;
}

int lowbit(int x)
{
    return x&(-x);
}
void add(int x,int k,int a[])
{
    while( x <= maxn )
    {
        a[x]+=k;
        x+=lowbit(x);
    }
}
int query(int x,int a[])
{
    int ans = 0;
    while(x)
    {
        ans+=a[x];
        x-=lowbit(x);
    }
    return ans;
}

int sum[maxn+10];
int num[maxn+10];


int main()
{
    cin>>n;
    rep(i,1,n) scanf("%d %d",&a[i].v,&a[i].pos);
    sort(a+1,a+1+n,cmp);
    LL ans = 0;
    Clean(sum,0);
    Clean(num,0);
    rep(i,1,n)  //计算左侧答案考虑相等情况
    {
        ans+= a[i].v *( (LL)a[i].pos*query( a[i].v , num ) - query( a[i].v , sum ) );
        add( a[i].v,1,num );
        add( a[i].v,a[i].pos,sum );
    }
    Clean(sum,0);
    Clean(num,0);
    Rrep(i,n,1)  //计算右侧答案,只考虑严格大于的情况,否则会加重
    {
        ans+= a[i].v *( query( a[i].v -1  , sum ) - (LL)a[i].pos*query( a[i].v - 1 , num ) );
        add( a[i].v,1,num );
        add( a[i].v,a[i].pos,sum );
    }
    cout<<ans<<endl;
    return 0;
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值