奶牛集会

版权声明:转载者乖乖♂站好 https://blog.csdn.net/Eric1561759334/article/details/83573552

题目

https://www.luogu.org/problemnew/show/P2345#sub

思路

将数组先按v值排序

然后找到中点mid,左右递归处理

因为v值排过序,所以右边的v值一定大于左边v值

就剩x不好算了

看到绝对值,最简单的方法就是去绝对值符号

所以我们应该枚举右边mid+1到r的区间,找到有哪些x值比当前小,哪些比它大

右边的v值一定大于左边v值,求和乘a[i].v就行了

但是,这又该怎么做呢?

也许左右x值为升序就好做了,这就像求逆序对一样

因此我们再对序列进行归并排序

尽管这会对v值的升序进行破环,但由于中间的划分,所以左右不会混合,前面那个 性质还能保证

代码

#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
using namespace std;
const int maxn=100000+10;
struct nod
{
    long long v,x;
}s[maxn];
struct node
{
    int left,right;
    long long sum1,sum2;
}t[maxn*4];
long long n,maxs,ans;
bool cmp(nod a,nod b)
{
    return a.v<b.v;
}
void build(int g,int l,int r)
{
    t[g].left=l;t[g].right=r;t[g].sum1=t[g].sum2=0;
    if(l==r) return;
    int mid=(l+r)>>1;
    build(g<<1,l,mid);build(g<<1|1,mid+1,r);
}
long long get(int g,int l,int r,int opt)
{
    if(r<t[g].left || t[g].right<l) return 0;
    if(l<=t[g].left&&t[g].right<=r)
    {
        if(opt==1) return t[g].sum1;
        if(opt==2) return t[g].sum2;
    }
    return get(g<<1,l,r,opt)+get(g<<1|1,l,r,opt);
}
void add(int g,int x,long long y)
{
    if(t[g].left==t[g].right)
    {
        t[g].sum1+=y;t[g].sum2++;return;
    }
    if(x<=t[g<<1].right) add(g<<1,x,y);
    else add(g<<1|1,x,y);
    t[g].sum1=t[g<<1].sum1+t[g<<1|1].sum1;
    t[g].sum2=t[g<<1].sum2+t[g<<1|1].sum2;
}
int main()
{
    scanf("%lld",&n);
    for(int i=1;i<=n;i++)
    {
        scanf("%lld%lld",&s[i].v,&s[i].x);
        maxs=max(maxs,s[i].x);
    }
    sort(s+1,s+n+1,cmp);
    build(1,1,maxs);
    for(int i=1;i<=n;i++)
    {
        long long g=get(1,s[i].x+1,maxs,1);//距离
        long long k=get(1,s[i].x+1,maxs,2);//个数
        ans+=s[i].v*(g-k*s[i].x);
        g=get(1,1,s[i].x-1,1);
        k=get(1,1,s[i].x-1,2);
        ans+=s[i].v*(k*s[i].x-g);
        add(1,s[i].x,s[i].x);
    }
    printf("%lld\n",ans);
}

猜你喜欢

转载自blog.csdn.net/Eric1561759334/article/details/83573552