APIO2015 八邻旁之桥

传送门

题目大意

两条水平的直线,距离为$1$,给定$n$对坐标,坐标是在某一条直线上的某一个位置,你可以建立$k$竖直的直线$(kleq 2)$,要使得每对坐标只沿着直线移动从第一个坐标到第二个坐标的距离和最小。

题解

首先可以求出在一对坐标在同一条水平直线上的代价,对于需要跨越的,都经过了竖直直线的$1$的距离,也都预先算出来。

考虑$k=1$的情况,设每对坐标的水平位置是$(x_1,x_2)$,直线放在了$pos$的位置,那么代价是$sum(|x_1-pos|+|x_2-pos|)$。

发现$x_1$,$x_2$对答案的贡献是独立的,且很显然当$pos$取所有$x_1,x_2$的中位数时,距离最小。

如何扩展到$k=2$?

设条竖直线的坐标是$pos_1,pos_2$(不妨设$pos_1<pos_2$),若$x_1,x_2$会选择$pos_1$,那么$|x_1-pos_1|+|x_2-pos_1|leq |x_1-pos_2|+|x_2-pos2|$,冷静分析一下不难得出只需比较$frac{x_1+x_2}{2}$与$pos_1,pos_2$的远近即可。

考虑对所有对坐标按照$x_1+x_2$,排序,这样一定满足选择$pos_1$的一定是一个前缀,选择$pos_2$的一定是一个后缀,那么问题就转化为两个$k=1$的子问题了。

直接枚举前缀后缀的划分位置,用权值线段树维护左右部分中位数大小,用前缀和优化算出所有数到中位数的距离即可。

复杂度$O(nlog n)$。

#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#define LL long long
#define M 100020
using namespace std;
namespace IO{
	int Top=0; char SS[20];
	void write(LL x){
		if(!x){putchar('0');return;} if(x<0) x=-x,putchar('-');
		while(x) SS[++Top]=x%10,x/=10;
		while(Top) putchar(SS[Top]+'0'),--Top;
	}
	int read(){
		int nm=0,fh=1; char cw=getchar();
		for(;!isdigit(cw);cw=getchar()) if(cw=='-') fh=-fh;
		for(;isdigit(cw);cw=getchar()) nm=nm*10+(cw-'0');
		return nm*fh;
	}
}
using namespace IO; char s1[20],s2[20];
int rt[2],n,m,A[M<<2],B[M<<2],K,cnt[2],od[M];
int G[M<<2],L[M<<2][2],R[M<<2][2],ct[M<<2][2];
LL res,sum[M<<2][2];
bool cmp(int x,int y){return A[x]+B[x]<A[y]+B[y];}
LL solve1(){
	sort(G+1,G+m+1); LL nw=0;
	for(int i=1;i<=m;i++) nw+=abs(G[m>>1]-G[i]);
	return nw;
}
void ins(int &x,int l,int r,int pos,int dt,int kd){
	if(!x) x=++cnt[kd]; ct[x][kd]+=dt,sum[x][kd]+=G[pos]*dt;
	if(l==r) return; int mid=((l+r)>>1);
	if(pos<=mid) ins(L[x][kd],l,mid,pos,dt,kd);
	else ins(R[x][kd],mid+1,r,pos,dt,kd);
}
int gtnum(int x,int l,int r,int rk,int kd){
	if(l==r) return l; int mid=((l+r)>>1);
	if(rk<=ct[L[x][kd]][kd]) return gtnum(L[x][kd],l,mid,rk,kd);
	return gtnum(R[x][kd],mid+1,r,rk-ct[L[x][kd]][kd],kd);
}
LL qry(int x,int l,int r,int pos,int kd){
	if(!ct[x][kd]) return 0ll;
	if(r<=pos) return (LL)ct[x][kd]*(LL)G[pos]-sum[x][kd];
	if(pos<=l) return sum[x][kd]-(LL)G[pos]*(LL)ct[x][kd];
	int mid=((l+r)>>1);
	return qry(L[x][kd],l,mid,pos,kd)+qry(R[x][kd],mid+1,r,pos,kd);
}
LL calc(LL kd){
	if(!ct[rt[kd]][kd]) return 0ll; int rk=(ct[rt[kd]][kd]+1); rk>>=1;
	int md=gtnum(rt[kd],1,m,rk,kd);
	return qry(rt[kd],1,m,md,kd);
}
LL solve2(){
	sort(od+1,od+n+1,cmp);
	sort(G+1,G+m+1),m=unique(G+1,G+m+1)-(G+1);
	for(int i=1;i<=n;i++){
		int t1=lower_bound(G+1,G+m+1,A[od[i]])-G;
		int t2=lower_bound(G+1,G+m+1,B[od[i]])-G;
		ins(rt[0],1,m,t1,1,0),ins(rt[0],1,m,t2,1,0);
	} LL ans=calc(0);
	for(int i=n;i;i--){
		int t1=lower_bound(G+1,G+m+1,A[od[i]])-G;
		int t2=lower_bound(G+1,G+m+1,B[od[i]])-G;
		ins(rt[0],1,m,t1,-1,0),ins(rt[0],1,m,t2,-1,0);
		ins(rt[1],1,m,t1,1,1),ins(rt[1],1,m,t2,1,1);
		ans=min(ans,calc(0)+calc(1));
	} return ans;
}
int main(){
	K=read(),n=read();
	for(int i=1;i<=n;i++){
		scanf("%s",s1),A[i]=read(),scanf("%s",s2),B[i]=read();
		if(A[i]>B[i]) swap(A[i],B[i]);
		if(s1[0]==s2[0]) res+=B[i]-A[i],i--,n--;
		else G[++m]=A[i],G[++m]=B[i],od[i]=i,res++;
	} write(((K&1)?solve1():solve2())+res);
	putchar('
');return 0;
}
原文地址:https://www.cnblogs.com/OYJason/p/9830299.html