SS241106B. 即便看不到未来
题意
给你一个无限大的三维空间,有 \(n\) 个点,每个点的坐标是 \((x_i,y_i,z_i)\),满足 \(n\le 5\times 10^5,|x_i|,|y_i|,|z_i| \le 10^9\)。你从 \((-inf,-inf,-inf)\) 出发,可以向三个正方向走。
定义攻击距离 \(k\),若你在 \((x,y,z)\),对于 \(\max\{ |x-x'|, |y-y'|, |z-z'| \}\) 的所有 \((x',y',z')\) 是可以攻击到的。问最小化的 \(k\) 使得你可以走一次攻击完所有点。
思路
介绍一个如果你想明白了就很好写的二分答案做法,不过代码可能稍长。
感觉很容易想到二分答案啊,然后一直假,而且细节太多,还需要丰富的想象力,赛场上调了 \(3h+\) 都没调出来。。。
显然二分答案 \(k\),考虑如何判断 \(k\) 是否合法。
相当于每个点 \((x_i,y_i,z_i)\) 存在一个左下角 \((x_i-k,y_i-k,z_i-k)\),右上角 \((x_i+k,y_i+k,z_i+k)\) 的正方体,你需要判断是否存在一条路径,满足坐标只加不减,经过所有正方体。
思考历程:
- 扫描线?
考虑先做二维的问题,可以扫描线 + 贪心做。一共有 \(O(n)\) 个正方形的顶点,建立右边为 \(x\) 轴正方向,上面为 \(y\) 轴正方向的坐标系,按照 \(x\) 坐标扫,如果一个正方形消失了,就走到这个正方形的最下面我能到达的那一行,然后清除一下途中经过的其他正方形。如果一个正方形消失的时候,我在它上边界的上面,我到达不了它,就返回不合法。
考虑三维的问题,建立上面为时间 \(x\) 正方向,右边为 \(y\) 轴正方向,前面为 \(z\) 轴正方向的三维坐标系。一开始我们在下左后角。然后每次正方形消失就贪心地走到最左后的地方。嗯?怎么感觉正确性是真的?好像真的是?但是最重要的问题是扫了一维,还剩两维怎么线段树维护啊?有这种科技吗?
\(\quad\) 2.1 开启观察模式
发现二维的问题,其实可以转化成,如果存在 \(i,j\),使得 \(i\) 的右下角在 \(j\) 的左上角的严格左上方,就是不合法的,否则一定合法,可以感性理解。
那么观察三维的问题,发现不那么好观察。
一个错误的结论是,如果存在 \(i,j\),使得 \(i\) 的第 \(p\) 维的下边界严格大于 \(j\) 的第 \(p\) 维的上边界,且 \(j\) 的第 \(q\) 维的下边界严格大于 \(i\) 的第 \(q\) 维的上边界,就返回不合法,否则合法。
这么做时间可以做到单次 check \(O(n)\) 的,可惜正确性错误。
\(\quad\) 2.2 错误之处
直接讲刚刚结论的错误之处,在于可能存在 \(i,j,k\)(此 \(k\) 非彼 \(k\))使得 \(j\) 的 \(x\) 维严格大于 \(i\),\(k\) 的 \(y\) 维严格大于 \(j\),\(x\) 的 \(z\) 维严格大于 \(k\)(这里【严格大于】指的是前者的下边界严格大于后者的上边界),即 \(j\) 在 \(i\) 上面偏后偏左,\(k\) 在 \(j\) 右边偏后偏左偏下,\(i\) 又在 \(k\) 前面。
可以通过这个观察发现,这是一个 \(i\to j\to k\to i\) 的环,可以观察得到下面的结论,然后就可以做了,当然有另一种更为直接的观察方法。
- 小样例启动!
实际上赛时我选择直接抛弃了刚刚的结论,因为我无法想出结论的错误之处并改正。但是在写暴力代码之前先尝试手模小样例的过程中得到了启发,我写了一些随便的数据,并试图排序一个合法的经过顺序,来得到样例输出。然后发现可以根据依赖关系判环来判定是否合法。
我们发现对于某一维,如果 \(i\) 的上界严格小于 \(j\) 的下界,即 \(p_i + 2k < p_j\)(对于维 \(p\)),那么必须先经过 \(i\) 再经过 \(j\),否则你先走到 \(j\) 你就无法进入正方体 \(i\) 了。
这样就有了若干个约束条件,然后你感觉如果满足所有约束条件,就一定可以保证答案合法了,事实上这个结论是正确的。很好证明,因为对于每一维剩下没有约束的点,任意两个点在这一维上都是有交的,那么你直接待在这个交的区域不走就可以了。
对 \(x,y,z\) 三维分别排序,对于其中一维 \(p\),排名第 \(i\) 的点的约束就是一段排名的前缀对应点必须在它之前经过。我们把关于第 \(p\) 维的约束条件叫做边 \(P\)。
求前缀的长度可以双指针,因为前缀长度随枚举右指针是递增的。
考虑无解(即有环)的情况有哪些。
- 边 \((X,Y),(Y,Z),(Z,X)\) 形成的环。
- 边 \((X,Y,Z),(X,Z,Y)\) 形成的环。
注意这里的点对是有序的。不过由于是环所以只判以上五种情况就可以了。
对于第 \(1\) 种,\((P_1,P_2)\),其实就是在 \(p_2\) 这一维里,要先经过 \(v\) 再经过 \(u\),而在 \(p_1\) 这一维里约束了先经过 \(u\) 再经过 \(v\)。那么可以在做 \(p_1\) 这一维的时候统计约束在 \(i\) 之前的那段前缀的点中,在 \(p_1\) 这一维的坐标最大值,找 \(u\) 在 \(p_2\) 维的对应约束前缀的时候,看看这段前缀及其约束的 \(p_1\) 坐标最大值是不是严格大于 \(p_u+2k\) 就可以了。简单来讲就是对每个点维护约束必须在其之前经过的点的 \(p_1\) 坐标最大值,对 \(p_2\) 维判断约束必须在其之前经过的点是否存在矛盾。
对于第二种,也是类似的,维护每个点,其约束的所有点(指约束了必须在其之前的所有点)的 \(x\) 坐标最大值,然后判断是否矛盾。
感觉讲不清楚?
时间复杂度 \(O(n\log n + n\log V)\)。
code
这结论太难猜了,我只能死命地对拍和手模,有没有什么高效的方法呢?也许需要更加丰富的经验?
这题我一共写了 \(8\) 个 cpp 文件,他们分别是:checker.cpp diffcheck.cpp duipai.cpp hunt.cpp hunt2.cpp huntbaoli.cpp shuju.cpp tmp.cpp
,我真是有耐心啊!其实心态崩了
#include<bits/stdc++.h>
#define sf scanf
#define pf printf
#define rep(x,y,z) for(int x=y;x<=z;x++)
#define per(x,y,z) for(int x=y;x>=z;x--)
using namespace std;
typedef long long ll;
namespace cute_chtholly {constexpr int N=5e5+7,V=2e9;void _max(int &a,int b) { a=max(a,b); }#define isdigit(x) (x>='0'&&x<='9')#define gc getchar_unlocked#define pc putchar_unlockedtemplate <typename T>void read(T &x) {char ch=gc();x=0;T fl=1;for(;!isdigit(ch);ch=gc()) if(ch=='-') fl=-1;for(;isdigit(ch);ch=gc()) x=(x<<3)+(x<<1)+(ch^48);x=x*fl;}template <typename T>void write (T x,char ch) {static int st[40];int top=0;do {st[top++]=x%10;x/=10;}while(x);while(top) pc(st[--top]^48);pc(ch);}int n;int a[3][N],rrk[3][N];int rk[3][N];int l=0,r=1e9;int K;bool cmpx(int p,int q) { return a[0][p] < a[0][q]; }bool cmpy(int p,int q) { return a[1][p] < a[1][q]; }bool cmpz(int p,int q) { return a[2][p] < a[2][q]; }int mxx[3][N];int mx[3][N],_mxx[3][N];bool check() {int k=K<<1;memcpy(mxx,_mxx,sizeof(mxx));int it=0;rep(i,1,n) _max(mxx[0][i],mxx[0][i-1]);rep(i,1,n) {int u=rk[0][i];while(a[0][rk[0][it+1]]+k < a[0][u]) ++it;if(a[2][u]+k < mx[0][it]) return 0;_max(mxx[1][rrk[1][u]],mxx[0][it]);_max(mxx[2][rrk[2][u]],mxx[0][it]);}it=0;rep(i,1,n) _max(mxx[1][i],mxx[1][i-1]);rep(i,1,n) {int u=rk[1][i];while(a[1][rk[1][it+1]]+k < a[1][u]) ++it;if(a[0][u]+k < mx[1][it]) return 0;_max(mxx[2][rrk[2][u]],mxx[1][it]);}it=0;rep(i,1,n) _max(mxx[2][i],mxx[2][i-1]);rep(i,1,n) {int u=rk[2][i];while(a[2][rk[2][it+1]]+k < a[2][u]) ++it;if(a[1][u]+k < mx[2][it]) return 0;if(a[0][u]+k < mxx[2][it]) return 0;_max(mxx[1][rrk[1][u]],mxx[2][it]);}it=0;rep(i,1,n) _max(mxx[1][i],mxx[1][i-1]);rep(i,1,n) {int u=rk[1][i];while(a[1][rk[1][it+1]]+k < a[1][u]) ++it;if(a[0][u]+k < mxx[1][it]) return 0;}return 1;}void main() {read(n);rep(i,1,n) read(a[0][i]),read(a[1][i]),read(a[2][i]), rk[0][i]=rk[1][i]=rk[2][i]=i;sort(rk[0]+1,rk[0]+n+1,cmpx);sort(rk[1]+1,rk[1]+n+1,cmpy);sort(rk[2]+1,rk[2]+n+1,cmpz);rep(j,0,2) rep(i,1,n) _mxx[j][i]=a[0][rk[j][i]], rrk[j][rk[j][i]]=i;rep(j,0,2) mx[j][0]=_mxx[j][0]=-V;rep(i,1,n) mx[0][i]=max(a[2][rk[0][i]],mx[0][i-1]), mx[1][i]=max(a[0][rk[1][i]],mx[1][i-1]), mx[2][i]=max(a[1][rk[2][i]],mx[2][i-1]);while(l<r) {K=(l+r)>>1;if(check()) r=K;else l=K+1;}write(r,'\n');}
}
int main() {#ifdef LOCALfreopen("my.out","w",stdout);#else freopen("hunt.in","r",stdin);freopen("hunt.out","w",stdout);#endifcute_chtholly :: main();
}