[APIO2015]八邻旁之桥

题意

一条东西走向的穆西河将巴邻旁市一分为二,分割成了区域 \(A\) 和区域 \(B\)

每一块区域沿着河岸都建了恰好 \(1000000001\) 栋的建筑,每条岸边的建筑都从 \(0\) 编号到 \(1000000000\)。相邻的每对建筑相隔 \(1\) 个单位距离,河的宽度也是 \(1\) 个单位长度。区域 \(A\) 中的 \(i\) 号建筑物恰好与区域 \(B\) 中的 \(i\) 号建筑物隔河相对。

城市中有 \(N\) 个居民。第 \(i\) 个居民的房子在区域 \(P_i\)\(S_i\) 号建筑上,同时他的办公室坐落在 \(Q_i\) 区域的 \(T_i\) 号建筑上。一个居民的房子和办公室可能分布在河的两岸,这样他就必须要搭乘船只才能从家中去往办公室,这种情况让很多人都觉得不方便。为了使居民们可以开车去工作,政府决定建造不超过 \(K\) 座横跨河流的大桥。

由于技术上的原因,每一座桥必须刚好连接河的两岸,桥梁必须严格垂直于河流,并且桥与桥之间不能相交。

当政府建造最多 \(K\) 座桥之后,设 \(D_i\) 表示第 \(i\) 个居民此时开车从家里到办公室的最短距离。请帮助政府建造桥梁,使得 \(D_1 + D_2 + \cdots + D_N\) 最小。

\(1 \leq K \leq 2 , 1 \leq N \leq 10^5\)

分析

参照租酥雨的题解。

这题看上去很不可做呀,但是很明显K≤2这个条件是非常重要的。

先考虑K=1怎么做。

首先,如果一个人的房子和办公室位于河的同一侧,那么这个人肯定是不受任何影响的,即他对答案的贡献永不变。我们先把这种人预处理了,然后剩下的全是必须要过河的。

那么现在有cnt个人要过河,第i个人要从Ai走到Bi,或者说,从Ai走到桥的位置pos,再从桥的位置pos走到Bi。说白了我们就是要求∑abs(Ai−pos)+abs(Bi−pos),可发现Ai与Bi其实无差别。

所以就把所有的Ai跟Bi放在一起排序,然后桥的位置就一定是排序后中位数的位置。暴力统计每个点到桥的距离,这样K=1就做完了。

现在来考虑K=2。

首先看这样一个结论:对每个人来说,他一定会走那座离(Ai+Bi)/2更近的桥。这个结论其实不需要证明,手玩一下即可。

那么我们把所有人按照(Ai+Bi)/2排序,那么一定是左边一部分人走左边的那座桥,右边的一部分人走右边的那座桥。

所以我们要对左右分别维护一个数据结构,支持快速插入、删除元素,并可以快速查询中位数、查询区间和。在这里splay和权值线段树都是不错的选择。

那么这题就做完啦。嗯哼。

我用函数式Treap实现,时间复杂度\(O(n \log n)\)

某神犇:好题之所以为好题,不仅要题好,而且数据要好。

那么这题的确是好题了,不仅有卡常数据,而且还有卡RE的小数据。

代码

开始我信心满满地交了几遍

#include<bits/stdc++.h>
#define rg register
#define il inline
#define co const
template<class T>il T read()
{
	rg T data=0;
	rg int w=1;
	rg char ch=getchar();
	while(!isdigit(ch))
	{
		if(ch=='-')
			w=-1;
		ch=getchar();
	}
	while(isdigit(ch))
	{
		data=data*10+ch-'0';
		ch=getchar();
	}
	return data*w;
}
template<class T>il T read(rg T&x)
{
	return x=read<T>();
}
typedef long long ll;
using std::cerr;
using std::endl;

co int N=1e5+100;
int root[2];
int tot;
namespace  T
{
	int ch[N<<2][2],siz[N<<2];
	ll val[N<<2],sum[N<<2];
	int pri[N<<2];
	
	il int newnode(rg ll v)
	{
		int x=++tot;
		ch[x][0]=ch[x][1]=0,siz[x]=1;
		val[x]=sum[x]=v;
		pri[x]=rand();
		return x;
	}
	
	il void pushup(rg int x)
	{
		siz[x]=siz[ch[x][0]]+1+siz[ch[x][1]];
		sum[x]=sum[ch[x][0]]+val[x]+sum[ch[x][1]];
	}
	
	il void split(rg int x,rg ll v,rg int&l,rg int&r)
	{
		if(!x)
		{
			l=r=0;
			return;
		}
		if(val[x]<=v)
		{
			l=x;
			split(ch[l][1],v,ch[l][1],r);
			pushup(l);
		}
		else
		{
			r=x;
			split(ch[r][0],v,l,ch[r][0]);
			pushup(r);
		}
	}
	
	il int merge(rg int x,rg int y)
	{
		if(!x||!y)
			return x+y;
		if(pri[x]>pri[y])
		{
			ch[x][1]=merge(ch[x][1],y);
			pushup(x);
			return x;
		}
		else
		{
			ch[y][0]=merge(x,ch[y][0]);
			pushup(y);
			return y;
		}
	}
	
	il void insert(rg int&t,rg int z)
	{
		rg int x,y;
		split(t,val[z],x,y);
		t=merge(x,merge(z,y));
	}
	
	il int erase(rg int&t,rg ll v)
	{
		rg int x,y,z;
		split(t,v-1,x,y);
		split(y,v,y,z);
		rg int res=y;
		y=merge(ch[y][0],ch[y][1]);
		t=merge(x,merge(y,z));
		ch[res][0]=ch[res][1]=0;
		pushup(res);
		return res;
	}
	
	il ll kth(rg int&t,rg int k)
	{
		if(k==siz[ch[t][0]]+1)
			return val[t];
		if(k<=siz[ch[t][0]])
			return kth(ch[t][0],k);
		else
			return kth(ch[t][1],k-siz[ch[t][0]]-1);
	}
	
	il ll query(rg int&t)
	{
		if(!t)
			return 0;
		rg ll v=kth(t,(siz[t]+1)/2);
		rg int x,y;
		split(t,v,x,y);
		rg ll res=v*siz[x]-sum[x]+sum[y]-v*siz[y];
		t=merge(x,y);
		return res;
	}
}
using namespace T;
using namespace std;

struct node
{
	ll s,t;
	
	il bool operator<(rg co node&nd)co
	{
		return s+t<nd.s+nd.t;
	}
}x[N];

int main()
{
//	freopen(".in","r",stdin);
//	freopen(".out","w",stdout);
	srand(20030506);
	rg int k,n;
	read(k),read(n);
	rg ll ans=0;
	for(rg int i=1;i<=n;++i)
	{
		rg char p[2],q[2];
		rg ll s,t;
		scanf("%s %lld %s %lld",p,&s,q,&t);
		if(p[0]==q[0])
		{
			ans+=abs(s-t);
			--n,--i;
			continue;
		}
		x[i].s=s,x[i].t=t;
	}
	ans+=n;
	sort(x+1,x+n+1);
	if(k==1)
	{
		vector<int>v;
		for(rg int i=1;i<=n;++i)
		{
			v.push_back(x[i].s);
			v.push_back(x[i].t);
		}
		sort(v.begin(),v.end());
		rg ll mid=v[(v.size()+1)/2-1];
		for(rg int i=0;i<v.size();++i)
			ans+=abs(mid-v[i]);	
		printf("%lld\n",ans);
		return 0;
	}
	for(rg int i=1;i<=n;++i)
	{
		insert(root[0],newnode(x[i].s));
		insert(root[0],newnode(x[i].t));
	}
	rg ll sum=query(root[0]);
	for(rg int i=n;i>=1;--i)
	{
		insert(root[1],erase(root[0],x[i].s));
		insert(root[1],erase(root[0],x[i].t));
		sum=min(sum,query(root[0])+query(root[1]));
	}
	printf("%lld\n",ans+sum);
	return 0;
}

无论怎么卡常数都要TLE。

然后看别人的题解发现只需要插入维护个sum数组记录就行了,不用删除。

接着我的代码的确没有TLE了,然而RE了两个点。

发现是这组数据:

1 1
B 457748887 B 796098674

于是又改,终于AC了。

#include<bits/stdc++.h>
#define rg register
#define il inline
#define co const
template<class T>il T read()
{
	rg T data=0;
	rg int w=1;
	rg char ch=getchar();
	while(!isdigit(ch))
	{
		if(ch=='-')
			w=-1;
		ch=getchar();
	}
	while(isdigit(ch))
	{
		data=data*10+ch-'0';
		ch=getchar();
	}
	return data*w;
}
template<class T>il T read(rg T&x)
{
	return x=read<T>();
}
typedef long long ll;
using std::cerr;
using std::endl;

co int N=2e5;
int root[2];
int tot;
namespace  T
{
	int ch[N<<2][2],siz[N<<2];
	ll val[N<<2],sum[N<<2];
	int pri[N<<2];
	
	il int newnode(rg ll v)
	{
		int x=++tot;
		ch[x][0]=ch[x][1]=0,siz[x]=1;
		val[x]=sum[x]=v;
		pri[x]=rand();
		return x;
	}
	
	il void pushup(rg int x)
	{
		siz[x]=siz[ch[x][0]]+1+siz[ch[x][1]];
		sum[x]=sum[ch[x][0]]+val[x]+sum[ch[x][1]];
	}
	
	il void split(rg int x,rg ll v,rg int&l,rg int&r)
	{
		if(!x)
		{
			l=r=0;
			return;
		}
		if(val[x]<=v)
		{
			l=x;
			split(ch[l][1],v,ch[l][1],r);
			pushup(l);
		}
		else
		{
			r=x;
			split(ch[r][0],v,l,ch[r][0]);
			pushup(r);
		}
	}
	
	il int merge(rg int x,rg int y)
	{
		if(!x||!y)
			return x+y;
		if(pri[x]>pri[y])
		{
			ch[x][1]=merge(ch[x][1],y);
			pushup(x);
			return x;
		}
		else
		{
			ch[y][0]=merge(x,ch[y][0]);
			pushup(y);
			return y;
		}
	}
	
	il void insert(rg int&t,rg int z)
	{
		rg int x,y;
		split(t,val[z],x,y);
		t=merge(x,merge(z,y));
	}
	
	il int erase(rg int&t,rg ll v)
	{
		rg int x,y,z;
		split(t,v-1,x,y);
		split(y,v,y,z);
		rg int res=y;
		y=merge(ch[y][0],ch[y][1]);
		t=merge(x,merge(y,z));
		ch[res][0]=ch[res][1]=0;
		pushup(res);
		return res;
	}
	
	il ll kth(rg int&t,rg int k)
	{
		if(k==siz[ch[t][0]]+1)
			return val[t];
		if(k<=siz[ch[t][0]])
			return kth(ch[t][0],k);
		else
			return kth(ch[t][1],k-siz[ch[t][0]]-1);
	}
	
	il ll query(rg int&t)
	{
		if(!t)
			return 0;
		rg ll v=kth(t,(siz[t]+1)/2);
		rg int x,y;
		split(t,v,x,y);
		rg ll res=v*siz[x]-sum[x]+sum[y]-v*siz[y];
		t=merge(x,y);
		return res;
	}
}
using namespace T;
using namespace std;

struct node
{
	ll s,t;
	
	il bool operator<(rg co node&nd)co
	{
		return s+t<nd.s+nd.t;
	}
}x[N];

ll sum[N];

int main()
{
//	freopen(".in","r",stdin);
//	freopen(".out","w",stdout);
	srand(20030506);
	rg int k,n;
	read(k),read(n);
	rg ll ans=0;
	for(rg int i=1;i<=n;++i)
	{
		rg char p[2],q[2];
		rg ll s,t;
		scanf("%s %lld %s %lld",p,&s,q,&t);
		if(p[0]==q[0])
		{
			ans+=abs(s-t);
			--n,--i;
			continue;
		}
		x[i].s=s,x[i].t=t;
	}
	ans+=n;
	sort(x+1,x+n+1);
	if(k==1)
	{
		if(!n)
		{
			printf("%lld\n",ans);
			return 0;
		}
		vector<int>v;
		for(rg int i=1;i<=n;++i)
		{
			v.push_back(x[i].s);
			v.push_back(x[i].t);
		}
		sort(v.begin(),v.end());
		rg ll mid=v[(v.size()+1)/2-1];
		for(rg int i=0;i<v.size();++i)
			ans+=abs(mid-v[i]);	
		printf("%lld\n",ans);
		return 0;
	}
	for(rg int i=1;i<=n;++i)
	{
		insert(root[0],newnode(x[i].s));
		insert(root[0],newnode(x[i].t));
		::sum[i]=query(root[0]);
	}
	ll sol=::sum[n];
	for(rg int i=n;i>=1;--i)
	{
		insert(root[1],newnode(x[i].s));
		insert(root[1],newnode(x[i].t));
		sol=min(sol,::sum[i-1]+query(root[1]));
	}
	printf("%lld\n",ans+sol);
	return 0;
}

posted on 2019-01-14 13:39  autoint  阅读(135)  评论(0编辑  收藏  举报

导航