P6716 [CCO2018] Gradient Descent

· · 题解

题目描述

一个大小为 R \times C 的棋盘上有 n 个棋子,每次询问 (x,y) 的返回值为 \sum(|x_i-x|+|y_i-y|)。至多询问 k 次,求最小的返回值。

### 思路 #### 60pts 注意到在 $x$ 轴上移动并不会改变 $y$ 轴上的总距离,反之亦然。而绝对值函数的和是一个不严格的单谷函数。故尝试在 $x$ 轴和 $y$ 轴上分别三分。 这样的询问最大次数为 $4\log_{\frac{3}{2}}10^7 \approx 160$ 。而前 40pts 只有一半的询问次数,共 60pts。 ```cpp int l=1,r=m; while(l<r){ int lmid=l+(r-l)/3,rmid=r-(r-l)/3; if(ask(1,lmid)<ask(1,rmid))r=rmid-1; else l=lmid+1; } resy=l; l=1,r=n; while(l<r){ int lmid=l+(r-l)/3,rmid=r-(r-l)/3; if(ask(lmid,resy)<ask(rmid,resy))r=rmid-1; else l=lmid+1; } resx=l; ans=ask(resx,resy); cout<<"! "<<ans; ``` #### 80pts 注意到我们没有必要使用三分,由于本题函数是离散的,我们可以使用二分,这样的询问最大次数为 $4\log_210^7\approx 94$。 ```cpp int l=1,r=m; while(l<r){ int mid=l+r>>1; int lmid=mid,rmid=min(mid+1,m); if(ask(1,lmid)<ask(1,rmid))r=rmid-1; else l=lmid+1; } resy=l; l=1,r=n; while(l<r){ int mid=l+r>>1; int lmid=mid,rmid=min(mid+1,n); if(ask(lmid,resy)<ask(rmid,resy))r=rmid-1; else l=lmid+1; } resx=l; ans=ask(resx,resy); cout<<"! "<<ans; ``` #### 100pts 注意到我们二分时并不需要关心另一个轴上的位置,于是可以一起二分,共用一个点。这样可以做到 $3\log_210^7 \approx 70$ 次询问,足以通过本题。 ```cpp int l=1,r=m,nl=1,nr=n; while(l<r||nl<nr){ int amid=l<r?l+r>>1:1,bmid=nl<nr?nl+nr>>1:1; int nans=ask(bmid,amid); if(l<r){ if(nans<ask(bmid,amid+1))r=amid; else l=amid+1; } if(nl<nr){ if(nans<ask(bmid+1,amid))nr=bmid; else nl=bmid+1; } } resx=nl,resy=l; ans=ask(resx,resy); cout<<"! "<<ans; ```