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