zoukankan      html  css  js  c++  java
  • [APIO2015]八邻旁之桥

    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;
    }
    

  • 相关阅读:
    四则运算2
    进度条博客
    随机生成30道100以内的四则运算题
    构建之法阅读笔记01
    自我介绍
    c# dataGridView cell添加下拉框
    Winform Combobox.Items tooltip
    中医和红外(北京第一个工作)
    pdf修复
    c# 导出数据到excel
  • 原文地址:https://www.cnblogs.com/Troverld/p/14611414.html
Copyright © 2011-2022 走看看