看到這道題,思考一下後發現要用二分答案。所以為什麼要用二分?
因為標籤有二分還在二分專題裡
因為對於 \(ans\) 來說,如果 \(ans\) 不行,那麼 \(ans-1\) 也一定不行;也就是說,答案滿足單調性,所以可以二分;
也是因為暴力明顯過不了
那麼對於平面上的一些點來說,如果我們用一個最小的矩形覆蓋所有點,那麼這個矩形的大小和位置都是固定的;
所以我們的目標就是三個等大的小正方形替換它,並保證所有點仍被覆蓋;
由於小正方形的邊與座標軸平行,而這個最小的矩形的每個邊上都至少有一個點(否則不滿足“最小”),所以每個小正方形一定至少有一條邊與大長方形重合;
而由於我們只有三個正方形,所以一定有一個正方形在角上;
到這裡,思路已經很清晰了:
二分小正方形邊長,dfs 四個角的四種情況,得出答案
完整程式碼
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=2e4+5;
const int inf=0x7fffffff;
struct tree{
int x,y,now;
bool operator < (const tree &a)const{//優先按照 x 升序,x 相等按 y 升序
if(x==a.x)return y<a.y;
return x<a.x;
}
}a[N];
int n,maxn;
void cap(int minx,int maxx,int miny,int maxy,int now){//對指定區間進行覆蓋,now記錄當前是第幾塊塑膠布
for(int i=1;i<=n;i++){
if(a[i].now)continue;
if(minx<=a[i].x&&a[i].x<=maxx&&miny<=a[i].y&&a[i].y<=maxy)
a[i].now=now;
}
}
void recap(int now){//取消點當前的now標記 (之前的不會清空)
for(int i=1;i<=n;i++){
if(a[i].now==now)a[i].now=0;
}
}
bool dfs(int now,int mid){
int minx=inf,maxx=-inf;
int miny=inf,maxy=-inf;
for(int i=1;i<=n;i++){
if(!a[i].now){//在未被覆蓋的點中尋找最大與最小 x y 座標
maxx=max(maxx,a[i].x);
minx=min(minx,a[i].x);
maxy=max(maxy,a[i].y);
miny=min(miny,a[i].y);
}
}
int lenx=maxx-minx;
int leny=maxy-miny;
if(max(lenx,leny)<=mid)return 1;//能夠覆蓋
if(now==3)return 0;//塑膠布用完了
cap(minx,minx+mid,miny,miny+mid,now);//左下角
if(dfs(now+1,mid))return 1;//尋找下一塊
recap(now);//回溯
cap(minx,minx+mid,maxy-mid,maxy,now);//左上角
if(dfs(now+1,mid))return 1;
recap(now);
cap(maxx-mid,maxx,miny,miny+mid,now);//右下角
if(dfs(now+1,mid))return 1;
recap(now);
cap(maxx-mid,maxx,maxy-mid,maxy,now);//右上角
if(dfs(now+1,mid))return 1;
recap(now);
return 0;
}
bool check(int x){
for(int i=1;i<=n;i++)a[i].now=0;//清空覆蓋狀態
return dfs(1,x);//dfs
}
int solve(int l,int r){//喜歡打遞迴版的二分答案
int mid=(l+r)>>1;
if(r<=l)return mid;
if(check(mid))return solve(l,mid);
else return solve(mid+1,r);
}
int main(){
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i].x>>a[i].y;
}
sort(a+1,a+1+n);//記得排序
cout<<solve(1,inf);
return 0;
}