1. 程式人生 > >奶牛集會

奶牛集會

題目

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);
}