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