LOJ #2776. 「BalticOI 2018」蠕虫之忧
拼图题/fn
首先考虑先搞一个通解出来。
考虑一维的情况,显然是二分,设区间\([l,r]\),询问\(mid\)和\(mid+1\)的大小关系,如果\(H_{mid}<H_{mid+1}\),则\([mid+1,r]\)一定有一个局部最大值,否则\([l,mid]\)一定有一个局部最大值。
再考虑扩展到二维的情况。还是二分一条线,询问出这条线上的最大值,然后看最大值和左右两边的值的关系,哪边更大走哪边。同样可以扩展到三维,分别是\(O(n\log)\)和\(O(n^2\log n)\)的询问次数,可以过\(1,3,5\)三个子任务。
然后开始拼图。
考虑一维的情况,可以发现每次二分问两个很浪费。考虑如果\(H_{mid}<\max(H_l,H_r)\)其实是没有必要问第二遍的,因为较大的和它之间一定有一个,所以中间的\(mid\)随机扰动一下询问次数会变少,可以卡进\(35\)次。
但是实际上还有更优的做法。考虑按照黄金分割\(\phi=\frac{\sqrt 5+1}{2}\)二分,取中点\(mid1=0.618l+0.382r,mid2=0.382l+0.618r\),然后比较大小关系。这样看似比直接二分还劣,但是每次缩小后当前的另点实际上是接下来的区间的其中一个中点,也就是说只需要询问\(\log _{\phi}n\)次即可,低于\(2\log _2n\)次的直接二分。这样子是\(29\)次。
然后考虑二维的情况,可以发现其实我们可以不止划分一维,两维都可以划。这样轮换划可以将询问次数降低到\(3n+O(1)\)级别,可以通过子任务\(4\)。注意的是如果当前划的这一维的最大值不及之前的最大值,那么要按照之前的最大值来划分,而不能根据当前的最大值。因为当前最大值所对应的局部最大值点可能会跑出去。
最后考虑三维的情况。简单扩展二维是不行的因为连询问一个平面的次数都没有。
我们考虑一个暴力,随机\(Q\)个点取最大作为答案。再考虑另外一个暴力:随机一个点,然后往高的地方爬。
这两个暴力都是错的,但是结合一下就可以了。先随机\(\frac{Q}{2}\)个点,取最大的,然后暴力爬。
考虑其错误的概率。假设最劣情况每次需要\(5\)步来确定一个点周围的点,则在\(\frac{Q}{2}\)个点中不能出现大小前\(\frac{Q}{10}\)的点,这样的概率是\((1-\frac{Q}{10NMK})^{\frac{Q}{2}}\),在子任务\(6\)下约为\(1.2\times 10^{-4}\),可以认为是正确的。实际上跑的时候随机点的顺序询问就可以将每个点的询问次数降到期望\(2.5\)次,则失败的概率更低。
code:
#include<bits/stdc++.h>
#define Gc() getchar()
#define Me(x,y) memset(x,y,sizeof(x))
#define Mc(x,y) memcpy(x,y,sizeof(x))
#define d(x,y) ((m)*(x-1)+(y))
#define R(n) (rnd()%(n)+1)
#define Pc(x) putchar(x)
#define LB lower_bound
#define UB upper_bound
#define PB push_back
using ll=long long;using db=double;using lb=long db;using ui=unsigned;using ull=unsigned ll;
using namespace std;const int N=2666+5,M=170+5,K=1e5+5,mod=998244353,Mod=mod-1;const ll INF=1e18+7;const db eps=1e-5;mt19937 rnd(time(0));
int n,m,k,Q;map<int,int> f;
int GI(int x,int y,int z){return (x-1)*m*k+(y-1)*k+z-1;}
int Qry(int x,int y,int z){if(x<1||y<1||z<1||x>n||y>m||z>k) return 0;int Id=GI(x,y,z);if(f.count(Id))return f[Id];Q--;assert(Q>=0);cout<<"? "<<x<<' '<<y<<' '<<z<<endl;cin>>f[Id];return f[Id];}
void Succ(int x,int y,int z){cout<<"! "<<x<<' '<<y<<' '<<z<<endl;exit(0);}
int main(){
cin>>n>>m>>k>>Q;int i,j,h;
if(m==1&&k==1){
int l=1,r=n;while(l+1<r){
int mid=l+r>>1;
if(Qry(l,1,1)>=Qry(mid,1,1)) r=mid;
else if(Qry(r,1,1)>=Qry(mid,1,1)) l=mid;
else if(Qry(mid,1,1)<=Qry(mid+1,1,1)) l=mid;else r=mid;
}
int Mx=0,Id=0;for(int i=l;i<=r;i++) {int p=Qry(i,1,1);p>Mx&&(Mx=p,Id=i);}Succ(Id,1,1);
}
if(k==1){
int l1=1,l2=1,r1=n,r2=m,M1=0,I1=0,M2=0,I2=0,Ix1=0,Ip1=0,Ix2=0,Ip2=0;
while(l1+1<r1){
if(l1+1<r1){
int mid=l1+r1>>1;M1=0,I1=0;for(i=l2;i<=r2;i++) Qry(mid,i,1)>M1&&(M1=Qry(mid,i,1),I1=i);
int p1=Qry(mid+1,I1,1),p2=Qry(mid-1,I1,1);if(M1>=p1&&M1>=p2&&M1>=Qry(mid,I1-1,1)&&M1>=Qry(mid,I1+1,1))Succ(mid,I1,1);
if(M1<Ix2) {
if(Ip2<=mid) r1=mid;else l1=mid;
}
else if(M1<=p1) l1=mid;else r1=mid;
M1>Ix1&&(Ix1=M1,Ip1=I1);
}
if(l2+1<r2){
int mid=l2+r2>>1;M2=0,I2=0;for(i=l1;i<=r1;i++) Qry(i,mid,1)>M2&&(M2=Qry(i,mid,1),I2=i);
int p1=Qry(I2,mid+1,1),p2=Qry(I2,mid-1,1);if(M2>=p1&&M2>=p2&&M2>=Qry(I2-1,mid,1)&&M2>=Qry(I2+1,mid,1))Succ(I2,mid,1);
if(M2<Ix1){
if(Ip1<=mid) r2=mid;else l2=mid;
}
else if(M2<=p1) l2=mid;else r2=mid;
M2>Ix2&&(Ix2=M2,Ip2=I2);
}
}
int Mx=0;I1=0,I2=0;for(i=l1;i<=r1;i++) for(j=l2;j<=r2;j++) Qry(i,j,1)>Mx&&(Mx=Qry(i,j,1),I1=i,I2=j);Succ(I1,I2,1);
}
int Mx=0,x=0,y=0,z=0;for(i=1;i<=Q/2;i++){
int I1=R(n),I2=R(m),I3=R(k),p=Qry(I1,I2,I3);p>Mx&&(Mx=p,x=I1,y=I2,z=I3);
}while(1){
int p1=Qry(x,y,z+1),p2=Qry(x,y,z-1),p3=Qry(x,y-1,z),p4=Qry(x,y+1,z),p5=Qry(x+1,y,z),p6=Qry(x-1,y,z);
if(Mx>=p1&&Mx>=p2&&Mx>=p3&&Mx>=p4&&Mx>=p5&&Mx>=p6) Succ(x,y,z);
if(Mx<p1) Mx=p1,z++;else if(Mx<p2) Mx=p2,z--;else if(Mx<p3) Mx=p3,y--;
else if(Mx<p4) Mx=p4,y++;else if(Mx<p5) Mx=p5,x++;else Mx=p6,x--;
}
}

浙公网安备 33010602011771号