CF685C Optimal Point
CF685C
在一个三维空间中有若干个点。
你要找到一个整点使得它与若干个整点的曼哈顿距离最大值最小。
\(n\le 10^5\)
\(坐标\le 10^{18}\)
简单题+调半天
首先可以类比二维平面的曼哈顿距离和切比雪夫距离的转化:\((x,y)\)到原点的曼哈顿距离为\(|x|+|y|\),切比雪夫距离为\(max(|x|,|y|)\)。那么\((x,y)\)到原点的曼哈顿距离相当于\((x+y,x-y)\)到原点的切比雪夫距离。具体的推法考虑\(|x|=max(x,-x)\),分类讨论将四种情况写出来求最大值,发现可以用切比雪夫距离来表示。
但放到三维,曼哈顿距离为\(|x|+|y|+|z|\)。这个东西实际上不能转化成切比雪夫距离。但这个套路还可以利用:根据每一维的正负号分成八类,然后这个点可以从三维空间映射到一个四维空间。
形象地说,原点一定距离以内的点形成一个八面体。然后每个平面形如\(x+y+z=d\)或\(x+y-z=d\)这种形式。
显然这题要二分答案,二分答案之后求这些八面体的交集。
将它映射到的四维空间求个交集,于是就得到了\(x+y+z,x+y-z,x-y+z,x-y-z\)的范围。
然而求出交集还不一定有解。因为这四个维度并不是互不相干的。
设\(a=x+y-z,b=x-y+z,c=x-y-z\),那么有\(x+y+z=a+b-c\),\(x=\frac{a+b}{2},y=\frac{a-c}{2},z=\frac{b-c}{2}\)
问题变成了:找到\(a,b,c\)满足\(a\equiv b\equiv c \pmod 2\),\(a+b-c,a,b,c\)在各自的范围内。
前面这个同余的性质好做,枚举余数\(r\),然后将\(a\)映射到\(\frac{a-r}{2}\),相应的区间也改一下。
后面的重点是要让\(a+b-c\)在范围内。先让\(a,b,c\)取到最小值,如果不满足\(a+b-c\)的限制,那么把\(a\)调大,不行就再把\(b\)调大,还不行就再把\(c\)调小。由于通过这种方式我们得到的\(a+b-c\)的值是连续的,所以如果找不到一定无解。
细节有点多。。。(还有吐槽一下坐标开到\(10^{18}\),二分范围\(6*10^{18}\),直接打mid=l+r>>1
会挂的。。。解决的话可以开ull
或者写mid=l+(r-l>>1)
)
以下的代码有非常多得到调试语句,所以显得特别长。。。
using namespace std;
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>
#include <cassert>
#include <climits>
#define N 100010
#define ll long long
#define ull unsigned long long
int n;
struct DOT{long long x[3];} d[N],ans;
bool in(ll x,ll l,ll r){return l<=x && x<=r;}
ll m[8],m_[8];
void calc(ll a,ll b,ll c,ll r){
a=2*a+r,b=2*b+r,c=2*c+r;
assert(m[0]<=a+b-c && a+b-c<=m[4]);
ans.x[0]=a+b>>1;
ans.x[1]=a-c>>1;
ans.x[2]=b-c>>1;
assert(in(a,m[1],m[5]));
assert(in(b,m[2],m[6]));
assert(in(c,m[3],m[7]));
}
bool judge(ll l){
for (int i=0;i<4;++i)
m[i]=LLONG_MIN,m[i+4]=LLONG_MAX;
for (int i=1;i<=n;++i){
for (int j=0;j<4;++j){
ll t=d[i].x[0];
for (int k=1;k<3;++k)
t+=d[i].x[k]*(j>>2-k&1?-1:1);
m[j]=max(m[j],t-l);
m[j+4]=min(m[j+4],t+l);
}
}
for (int i=0;i<4;++i)
if (m[i]>m[i+4])
return 0;
// for (int i=0;i<4;++i)
// cout<<(long double)m[i]<<" "<<(long double)m[i+4]<<endl;
// printf("%.0lf %.0lf\n",(double)m[i],(double)m[i+4]);
for (int r=0;r<=1;++r){
bool cant=0;
for (int i=0;i<4;++i){
m_[i]=(m[i]-r)+1>>1;
m_[i+4]=(m[i+4]-r)>>1;
if (m_[i]>m_[i+4])
cant=1;
}
if (cant) continue;
// for (int i=0;i<4;++i)
// cout<<(long double)m[i]<<" "<<(long double)m[i+4]<<endl;
// printf("%.0lf %.0lf\n",(double)m_[i],(double)m_[i+4]);
ll a=m_[1],b=m_[2],c=m_[7];
// printf("%.0lf\n",(double)a+b-c);
if (in(a+b-c,m_[0],m_[4])){
calc(a,b,c,r);
return 1;
}
ll t=m_[0]-(a+b-c);
// printf("%.0lf %.0lf\n",(double)a+b-c+t,(double)a+t);
if (in(a+t,m_[1],m_[5])){
calc(a+t,b,c,r);
return 1;
}
else
t-=m_[5]-a,a=m_[5];
// printf("%.0lf %.0lf\n",(double)a+b-c+t,(double)b+t);
if (in(b+t,m_[2],m_[6])){
calc(a,b+t,c,r);
return 1;
}
else
t-=m_[6]-b,b=m_[6];
// printf("%.0lf %.0lf\n\n",(double)a+b-c+t,(double)c-t);
if (in(c-t,m_[3],m_[7])){
calc(a,b,c-t,r);
return 1;
}
// a=m_[5],b=m_[6],c=m_[3];
// if (in(a+b-c,m_[0],m_[4])){
// calc(a,b,c,r);
// return 1;
// }
// t=(a+b-c)-m_[4];
// if (in(a-t,m_[1],m_[5])){
// calc(a-t,b,c,r);
// return 1;
// }
// if (in(b-t,m_[2],m_[6])){
// calc(a,b-t,c,r);
// return 1;
// }
// if (in(c+t,m_[3],m_[7])){
// calc(a,b,c+t,r);
// return 1;
// }
}
return 0;
}
int main(){
freopen("in.txt","r",stdin);
int T;
scanf("%d",&T);
while (T--){
scanf("%d",&n);
for (int i=1;i<=n;++i)
scanf("%lld%lld%lld",&d[i].x[0],&d[i].x[1],&d[i].x[2]);
// printf("%.0lf\n",(double)tmp);
ll l=0,r=6000000000000000000,res=-1;
// printf("%d\n",judge(r));
while (l<=r){
ll mid=(ull)l+(ull)r>>1;
if (judge(mid))
r=(res=mid)-1;
else
l=mid+1;
}
// printf("%lld\n",(long long)res);
judge(res);
ll tmp=0;
for (int i=1;i<=n;++i)
tmp=max(tmp,abs(ans.x[0]-d[i].x[0])+abs(ans.x[1]-d[i].x[1])+abs(ans.x[2]-d[i].x[2]));
// printf("%lld\n",tmp);
printf("%lld %lld %lld\n",ans.x[0],ans.x[1],ans.x[2]);
}
return 0;
}