洛谷 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;
}
原文地址:https://www.cnblogs.com/yinyuqin/p/15318940.html