洛谷 P2345 [USACO04OPEN]MooFest G(二维偏序,归并排序)
传送门
解题思路
数据范围很小,可以直接暴力,但是为了练习而练习,将数据范围自动扩大十倍处理。
公式不好直接求,想办法将其化作可以直接求的公式。
首先把每头奶牛按照v从小到大排序,这样保证了v这一维是有序的,从前向后枚举时,\(max(v_i,v_j)=v_j (i<j)\)。
再去掉x这一维就需要用到逆序对(也就是二维偏序思想),将绝对值分为>0和<0两种情况。
在归并排序的过程中,记录左半部分已经用到了和未用到的奶牛的数量和v的和,这样在每轮到右边的奶牛时,都可以直接计算右边这个奶牛在整个区间里的贡献。
而这个贡献又分为左半部分已经用过的奶牛和未用过的奶牛(即v小于右边的奶牛和v大于右边的奶牛)。
AC代码
#include<cstdio>
#include<iostream>
#include<cstring>
#include<iomanip>
#include<cmath>
#include<algorithm>
using namespace std;
const int maxn=2e4+5;
int n;
long long d[maxn],ans;
struct node{
long long v,x;
}a[maxn],b[maxn];
bool cmp(node a,node b){
return a.v<b.v;
}
void divide(int l,int r){
if(l==r) return;
int mid=(l+r)/2;
divide(l,mid);
divide(mid+1,r);
int now=l,nowl=l,nowr=mid+1,cntl=0,cntr=mid-l+1;
long long suml=0,sumr=d[mid]-d[l-1];
while(nowl<=mid||nowr<=r){
if(nowr<=r&&(nowl>mid||a[nowr].x<a[nowl].x)){
ans+=a[nowr].v*(cntl*a[nowr].x-suml+sumr-cntr*a[nowr].x);
b[now++]=a[nowr++];
}else{
cntl++;
suml+=a[nowl].x;
cntr--;
sumr-=a[nowl].x;
b[now++]=a[nowl++];
}
}
for(int i=l;i<=r;i++) a[i]=b[i];
}
int main(){
ios::sync_with_stdio(false);
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i].v>>a[i].x;
sort(a+1,a+n+1,cmp);
for(int i=1;i<=n;i++) d[i]=d[i-1]+a[i].x;
divide(1,n);
cout<<ans;
return 0;
}