XXI.[APIO2015]八邻旁之桥

首先先忽略所有在同侧的人,考虑异侧的人。

则明显,如果我们只在\(p\)位置修一座桥,则一个从某侧的\(x\)到另一侧的\(y\)的人,其一共要走的距离就是

\[|p-x|+|p-y| \]

(忽略了桥长,因为桥长可以被统一计算)

于是我们发现,此时\(x\)\(y\)是独立的。于是问题就转成了数轴上有很多点,要求一个点 \(p\) 到所有点的距离和最小。

通过逐步调整法,我们可以发现\(p\)即为所有点的中位数。(因为点的数量是偶数,所以会有两个中位数,此时选择任何一个都是可以的)

进一步拆开绝对值符号之后,会发现是\((\text{更大的一半数之和})-(\text{更小的一半数之和})\)

现在有两座桥了。我们会发现,假如我们对所有人按照\(x+y\)排序,则一定是前一半的人走左侧的桥,后一半人走右侧的桥。这可以通过画出图来证明:两端都在左侧桥左方的人,或是都在右侧桥右方的人,显然走相应的桥最优;两端各在两侧桥两边的人,显然走任何一座桥都是可以的;唯独两端在两侧桥之间的人,画出图来,会发现走离其更近的桥更优,而比较就是按\(x+y\)比较的。

于是我们现在就可以枚举这个断点,两边就分别转成了一座桥的问题。于是我们现在就要对于每个前缀和后缀,求出其中\((\text{更大的一半数之和})-(\text{更小的一半数之和})\),并将两边拼在一起。

如何求出呢?

平衡树

那就太可怕了。直接使用两个堆即可,其中一个是大根堆,维护前一半数;一个是小根堆,维护后一半数。当加入一个人时,先把其\(x\)\(y\)全部插入大根堆,然后再平衡两个堆的大小即可。假如发现平衡过后,大根堆的顶大于小根堆的顶,就交换堆顶元素即可。

时间复杂度\(O(n\log n)\)

代码:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int n,m,r;
pair<int,int>p[100100];
vector<int>v[2];
char s[4];
ll all,mn=0x3f3f3f3f3f3f3f3f,pre[100100],suf[100100],PRE,SUF;
priority_queue<int>P,Q;//P:greater heap for smaller part; Q:smaller heap for greater part
int main(){
	scanf("%d%d",&m,&r);
	for(int i=1,x,y;i<=r;i++){
		scanf("%s%d",s,&x);
		scanf("%s%d",s+1,&y);
		if(s[0]==s[1])all+=abs(x-y);
		else p[++n]=make_pair(x,y);
	}
	all+=n;
	sort(p+1,p+n+1,[](pair<int,int>u,pair<int,int>v){return u.first+u.second<v.first+v.second;});
	while(!P.empty())P.pop();while(!Q.empty())Q.pop();PRE=SUF=0;
	for(int i=1;i<=n;i++){
		PRE+=p[i].first,PRE+=p[i].second;
		P.push(p[i].first),P.push(p[i].second);
		while(P.size()!=Q.size())PRE-=P.top(),SUF+=P.top(),Q.push(-P.top()),P.pop();
		while(P.top()>-Q.top()){
			int x=P.top(),y=-Q.top();
			PRE-=x,PRE+=y;
			SUF+=x,SUF-=y;
			P.pop(),Q.pop();
			P.push(y),Q.push(-x);
		}
		pre[i]=SUF-PRE;
	}
	while(!P.empty())P.pop();while(!Q.empty())Q.pop();PRE=SUF=0;
	for(int i=n;i>=1;i--){
		PRE+=p[i].first,PRE+=p[i].second;
		P.push(p[i].first),P.push(p[i].second);
		while(P.size()!=Q.size())PRE-=P.top(),SUF+=P.top(),Q.push(-P.top()),P.pop();
		while(P.top()>-Q.top()){
			int x=P.top(),y=-Q.top();
			PRE-=x,PRE+=y;
			SUF+=x,SUF-=y;
			P.pop(),Q.pop();
			P.push(y),Q.push(-x);
		}
		suf[i]=SUF-PRE;		
	}
	if(m==1)mn=min(pre[n],suf[1]);
	else for(int i=0;i<=n;i++)mn=min(mn,pre[i]+suf[i+1]);
	printf("%lld\n",all+mn);
	return 0;
}