首先先忽略所有在同侧的人,考虑异侧的人。
则明显,如果我们只在\(p\)位置修一座桥,则一个从某侧的\(x\)到另一侧的\(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;
}