POJ1723 LuoguP1889 SOLDIERS 士兵站队
这道题洛谷评分这么低我是没想到的。感觉这是目前我做得最巧妙的橙题了。
先简述一下题目:
平面内有随机分布的 \(N\) 个格点 \((x,y)\) ,点不可重合。每次操作可以选一个点朝 上/下/左/右 四个方向移动一格。问把这些点移动成一条连续的、平行于 \(x\) 轴的线段的最小操作数。
我们一步一步来解决这道题。
首先,我们把我们要描述的对象数学化。不妨设最终这 \(N\) 个点中最左边的点坐标为 \((X,Y)\),那么这些点就可以描述为 \((X,Y),(X+1,Y),(X+2,Y),\dots ,(X+N-1,Y)\)。首先我们可以发现,点 \((x,y)\) 纵向(平行于 \(y\) 轴)的运动距离最短是 \(|Y-y|\)。
所以我们把纵向运动分离出来,用序列 \(A\) 记录每个点的纵坐标,得到问题:
- 有一个序列 \(A=(A_1,A_2,\dots , A_N)\)。求 \(\min \limits_{Y\in\mathbb R}\Big\{{\sum \limits_{i=1}^{N}{|Y-A_i|} }\Big\}\)。
这个问题不就是货仓选址(AcWing104)嘛!参考蓝书:
也有另一种证明,适合实数域,也更优美:(先把 \(A\) 由小到大排序,因为显然顺序不影响最终答案)
即求下式的最小值:
利用绝对值三角不等式 \(|a|+|b|\geq |a\pm b|\) 得:
当 \(2\ \Big|\ N\) 时,\(Y\in [\frac N2,\frac N2 +1]\) 时取等;否则,\(Y=\frac {N+1}2\) 时取等。
现在我们来解决横向移动。这个麻烦很多。我们设一个大小为 \(N\) 的序列 \(A\) 记录 \(N\) 个点最终顺序下的原来的横坐标。注意,现在 \(A\) 是一个不确定的序列。
即,若点 \((x,y)\) 最终移到了 \((X+i,Y)\) 的位置,那么 \(A_i=x\)。
那么最终横向的移动距离为:
显然因为 \(X\) 毕竟是常量(只是不确定),我们令序列 \(B=(A_1-X,A_2-X,\dots ,A_N-X)\),元素相对位置大小不改变,则 \(Distance=\sum\limits _{i=1}^{N}{|B_i-i|}\)。
这里我们采用邻项交换的思想。如果我们交换 \(B_i\) 和 \(B_{i+1}\), 那么最终移动距离分别为 \(|B_i-i|+|B_{i+1}-i-1|\) 和 \(|B_{i+1}-i|+|B_i-i-1|\)。
比较 \(|B_i-i|+|B_{i+1}-i-1|\) 和 \(|B_{i+1}-i|+|B_i-i-1|\),只需比较 \(|B_i-i|-|B_i-i-1|\) 和 \(|B_{i+1}-i|-|B_{i+1}-i-1|\)。设 \(f(x)=|x-i|+|x-i-1|,i\in [1,N]\),则其又可以表示为分段函数:
如图是当 \(i=2.2\) 时的 \(f(x)\) 图象:

现在问题转化为了:当 \(f(B_i)\leq f(B_{i+1})\) 时,\(|B_i-i|+|B_{i+1}-i-1|\leq |B_{i+1}-i|+|B_i-i-1|\)。
我们发现,\(f(x)\) 是一个单调不减函数。故 \(x_1\leq x_2\) 时,\(f(x_1)\leq f(x_2)\)。即 \(B_i\leq B_{i+1}\) 时比 \(B_i\geq B_{i+1}\) 时更优或等价。这就告诉我们,当 \(B\) 不下降,也就是 \(A\) 不下降时,原式取得最小。
现在 \(A\) 成为一个确定的序列了,\(A_i\) 等于第 \(i\) 大的横坐标,那么 \(A_i-i\) 就是一个定值,我们设序列 \(C,C_i=A_i-i\)。我们再来试着求最小值。改写一下最终移动距离的公式。
它现在就变成了纵向移动的公式!现在会求了吧!就是要求:
找序列 \(C\) 的中位数即可。
代码:
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
#include<iostream>
using namespace std;
int n,x[10055],y[10055],ans;
signed main(){
scanf("%d",&n);
for(int i=1;i<=n;i++)scanf("%d%d",x+i,y+i);
sort(x+1,x+1+n);
//求得确切的序列 A
for(int i=1;i<=n;i++)x[i]-=i;
//现在 x 数组描述的就是序列 B
sort(x+1,x+1+n);sort(y+1,y+1+n);
for(int i=1;i<=n;i++)
ans+=abs(x[i]-x[(n>>1)+1])+abs(y[i]-y[(n>>1)+1]);
printf("%d",ans);
return 0;
}

浙公网安备 33010602011771号