【cdq分治】MooFest G 加强版

洛谷p5094

cdq分治思想(二维偏序)

将其中一维排序后,分离条件的相关性。将左半边的子问题用于解决右半边的子问题。
左半边和右半边靠递归完成,专注于处理跨越部分。
[l,r]工作完成后,可以根据需要选择将其按第二关键字(或任意顺序)排序,而不影响后续。

方法一:按x作为第一关键字排序

for j=mid+1 to r 枚举[l,j]的贡献和
由归并排序特点,[l,mid]和[mid+1,r]两个区间内已经按照第二关键字s排好序。寻找x<xj和x>xj的分界点,最后一个x<xj的点记为xi
贡献和为 v j ( ∑ k = l i ( x j − x k ) + ∑ k = i + 1 m i d ( x k − x j ) ) = v j ( ( i − l + 1 ) x j − s u m x [ l , i ] + s u m x [ i + 1 , m i d ] − ( m i d − i ) x j ) v_j(\sum_{k=l}^{i}(x_j-x_k)+\sum_{k=i+1}^{mid}(x_k-x_j) )=v_j((i-l+1)x_j-sumx[l,i]+sumx[i+1,mid]-(mid-i)x_j) vj(k=li(xjxk)+k=i+1mid(xkxj))=vj((il+1)xjsumx[l,i]+sumx[i+1,mid](midi)xj)
前缀和预处理,查询复杂度O(1)。
贡献加完后,对第二关键字归并排序。

#include<bits/stdc++.h>
using namespace std;
#define int long long
struct node{
	int x,v;
}a[50005];
bool cmp(node aa,node bb){
	return aa.v<bb.v;//按v从小到大排序 
}
int ans=0,sum[50005];
void merge(int l,int r){
	int mid=(l+r)/2;
	sum[l-1]=0;
	for(int i=l;i<=r;i++){
		sum[i]=sum[i-1]+a[i].x;
	}
	for(int j=mid+1;j<=r;j++){
		int ii=l-1;//i:寻找x<xj的所有x,i标志分界线(最后一个满足的):由于归并排序,已经按x从小到大排序 
		while(ii<mid&&a[ii+1].x<a[j].x){
			ii++;
		}
		ans+=a[j].v*((ii-l+1)*a[j].x-(sum[ii]-sum[l-1])+(sum[mid]-sum[ii])-(mid-ii)*a[j].x);
	}
	//中间跨越部分处理完成后,即可对合并的整体按第二关键字x排序,不影响后续结果。 
	node aux[r-l+1];
	for(int i=l;i<=r;i++){
		aux[i-l]=a[i];
	}
	int i1=l,i2=mid+1;
	for(int i=l;i<=r;i++){
		if(i1>mid){
			a[i]=aux[i2-l];
			i2++;
		}else if(i2>r){
			a[i]=aux[i1-l];
			i1++;
		}else if(aux[i1-l].x>=aux[i2-l].x){
			a[i]=aux[i2-l];
			i2++;
		}else{
			a[i]=aux[i1-l];
			i1++;
		}
	}
}
void mergesort(int l,int r){
	if(l>=r) return;
	int mid=(l+r)/2;
	mergesort(l,mid);
	mergesort(mid+1,r);
	merge(l,r);
}
signed main(){
	int n;
	cin>>n;
	for(int i=0;i<n;i++){
		cin>>a[i].v>>a[i].x;
	}
	sort(a,a+n,cmp);
	mergesort(0,n-1);
	cout<<ans;
}

方法二:按v作为第一关键字排序

与上同理,给出和式推导过程:
∑ k = l i v j ( x j − x k ) + ∑ k = i + 1 m i d v k ( x j − x k ) = v j [ ( i − l + 1 ) x j − s u m x [ l , i ] ] + x j s u m v [ i + 1 , m i d ] − s u m x v [ i + 1 , m i d ] \sum_{k=l}^{i}v_j(x_j-x_k)+\sum_{k=i+1}^{mid}v_k(x_j-x_k)=v_j[(i-l+1)x_j-sumx[l,i]]+x_jsumv[i+1,mid]-sumxv[i+1,mid] k=livj(xjxk)+k=i+1midvk(xjxk)=vj[(il+1)xjsumx[l,i]]+xjsumv[i+1,mid]sumxv[i+1,mid]

#include<bits/stdc++.h>
using namespace std;
#define int long long
struct node{
	int x,v;
}a[50005];
bool cmp(node aa,node bb){
	return aa.x<bb.x;//按x从小到大排序 
}
int ans=0,sum_x[50005],sum_v[50005],sum_xv[50005];
void merge(int l,int r){
	int mid=(l+r)/2;
	sum_x[l-1]=0,sum_v[l-1]=0,sum_xv[l-1]=0;
	for(int i=l;i<=r;i++){
		sum_x[i]=sum_x[i-1]+a[i].x;
		sum_v[i]=sum_v[i-1]+a[i].v;
		sum_xv[i]=sum_xv[i-1]+a[i].v*a[i].x;
	}
	for(int j=mid+1;j<=r;j++){
		int ii=l-1;//i:寻找v<vj的所有x,i标志分界线(最后一个满足的):由于归并排序,已经按x从小到大排序 
		while(ii<mid&&a[ii+1].v<a[j].v){
			ii++;
		}
		ans+=a[j].v*((ii-l+1)*a[j].x-(sum_x[ii]-sum_x[l-1]))+a[j].x*(sum_v[mid]-sum_v[ii])-(sum_xv[mid]-sum_xv[ii]);
	} 
	//中间跨越部分处理完成后,即可对合并的整体按第二关键字v排序,不影响后续结果。 
	node aux[r-l+1];
	for(int i=l;i<=r;i++){
		aux[i-l]=a[i];
	}
	int i1=l,i2=mid+1;
	for(int i=l;i<=r;i++){
		if(i1>mid){
			a[i]=aux[i2-l];
			i2++;
		}else if(i2>r){
			a[i]=aux[i1-l];
			i1++;
		}else if(aux[i1-l].v>=aux[i2-l].v){
			a[i]=aux[i2-l];
			i2++;
		}else{
			a[i]=aux[i1-l];
			i1++;
		}
	}
}
void mergesort(int l,int r){
	if(l>=r) return;
	int mid=(l+r)/2;
	mergesort(l,mid);
	mergesort(mid+1,r);
	merge(l,r);
}
signed main(){
	int n;
	cin>>n;
	for(int i=0;i<n;i++){
		cin>>a[i].v>>a[i].x;
	}
	sort(a,a+n,cmp);
	mergesort(0,n-1);
	cout<<ans;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值