【CF666D】Chain Reaction(暴搜+细节讨论)
- 给定平面直角坐标系内四个顶点\(p_i\),对于每个点选择与它横坐标相同或纵坐标相同的一个点\(p'_i\)。
- 要求\(p'_i\)是一个四边平行于坐标轴的正方形的四个顶点(不能退化成一点)。
- 求\(\max_{i=0}^3dis(p_i,p_i')\)的最小值并构造一组方案。
- 数据组数\(\le50\)
暴枚+初步分类讨论
首先,我们直接暴枚对于每个\(p_i\),\(p_i'\)是与它横坐标相同还是纵坐标相同,由此便可以得到四条与坐标轴平行的取点直线。
显然,如果有超过两条相同的直线(正方形中不可能有三点某一维坐标相同)或某个方向上有超过两条不同的直线(正方形一个方向上只有两种坐标)肯定无解。
而若去重后两个方向上都恰有一条直线,实际上这也是无解的。
通过上面的分析,发现剩余的情况必然有至少一个方向上恰有两条不同的直线,则正方形的边长\(d\)就固定为这两直线间距离。
不妨假设是有两条与\(x\)轴平行的直线,那么只需分三类讨论就行了。
\(2+2\)
显然交点必选,只要判断组成的是不是正方形即可。
注意,这里有一个偷懒的写法,求出正方形的一种顶点方案后,我们不用直接判断谁与谁配对,而可以写个全排列暴搜,这样可以省去不少细节。这在之后的情况中也同样适用。
\(2+1\)
同理两个交点必选,然后正方形就只有左右两个。
两种情况分别搜一下就好了。
\(2+0\)
相当于可以任选一个夹在这两条直线之间的正方形。
对于一条直线上两点,肯定让正方形左边的点和左边的点配对,正方形右边的点和右边的点配对。
假设正方形左边的横坐标为\(t\),那么右边横坐标就是\(t+d\)。
到左边\(x_L\)的距离就是\(|t-x_L|\),而到右边\(x_R\)的距离就是\(|t+d-x_R|=|t-(x_R-d)|\)。
所以我们只要把两条直线上的\(x_L,x_R-d\)放在一起求出最小值和最大值,那么\(t\)的最优取值就是它俩的平均数。
代码:\(O(T\cdot2^4\cdot4!)\)
#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
using namespace std;
int ans,x[4],y[4],sx[4],sy[4],gx[4],gy[4],ox[4],oy[4],id[9];map<int,int> cx,cy;
int u[4];I void Match(CI i=0,CI t=0)//全排列暴搜匹配方式
{
if(t>=ans) return;if(i==4) {for(RI j=0;j^4;++j) sx[j]=ox[j],sy[j]=oy[j];return (void)(ans=t);}
for(RI j=0;j^4;++j) !u[j]&&(u[j]=1,ox[j]=gx[i],oy[j]=gy[i],
(x[j]==gx[i]||y[j]==gy[i])&&(Match(i+1,max(t,abs(x[j]-gx[i])+abs(y[j]-gy[i]))),0),u[j]=0);//至少有一维相同
}
int kx[2],ky[2];I void dfs(CI i,RI tx=0,RI ty=0)//暴枚每个点哪维相同
{
#define T(x) (id[x&-x])//状压的第一个点编号
#define P(x) (id[x^(x&-x)])//状压的第二个点编号
#define Work(x0,y0,x1,y1,x2,y2,x3,y3) (gx[0]=x0,gy[0]=y0,gx[1]=x1,gy[1]=y1,gx[2]=x2,gy[2]=y2,gx[3]=x3,gy[3]=y3,Match())//根据一组顶点方案开始搜索
if(i==4)//暴枚完了
{
RI d;if(tx==1&&ty==1) return;if(tx==2&&ty==2)//1+1(无解);2+2
{
abs(kx[0]-kx[1])==abs(ky[0]-ky[1])&&(Work(kx[0],ky[0],kx[0],ky[1],kx[1],ky[0],kx[1],ky[1]),0);return;//如果是正方形就去搜
}
if(ty==1) return d=abs(kx[0]-kx[1]),//1+2
Work(kx[0],ky[0],kx[1],ky[0],kx[0],ky[0]+d,kx[1],ky[0]+d),Work(kx[0],ky[0],kx[1],ky[0],kx[0],ky[0]-d,kx[1],ky[0]-d);//上下两个
if(tx==1) return d=abs(ky[0]-ky[1]),//2+1
Work(kx[0],ky[0],kx[0],ky[1],kx[0]+d,ky[0],kx[0]+d,ky[1]),Work(kx[0],ky[0],kx[0],ky[1],kx[0]-d,ky[0],kx[0]-d,ky[1]);//左右两个
if(!ty)//0+2
{
int tmp[4]={y[T(cx[kx[0]])],y[P(cx[kx[0]])],y[T(cx[kx[1]])],y[P(cx[kx[1]])]};
tmp[0]>tmp[1]&&(swap(tmp[0],tmp[1]),0),tmp[2]>tmp[3]&&(swap(tmp[2],tmp[3]),0),
d=abs(kx[0]-kx[1]),tmp[1]-=d,tmp[3]-=d,sort(tmp,tmp+4);RI t=tmp[0]+tmp[3]>>1;//取最小值和最大值的平均数
return Work(kx[0],t,kx[1],t,kx[0],t+d,kx[1],t+d);
}
if(!tx)//2+0
{
int tmp[4]={x[T(cy[ky[0]])],x[P(cy[ky[0]])],x[T(cy[ky[1]])],x[P(cy[ky[1]])]};
tmp[0]>tmp[1]&&(swap(tmp[0],tmp[1]),0),tmp[2]>tmp[3]&&(swap(tmp[2],tmp[3]),0),
d=abs(ky[0]-ky[1]),tmp[1]-=d,tmp[3]-=d,sort(tmp,tmp+4);RI t=tmp[0]+tmp[3]>>1;//取最小值和最大值的平均数
return Work(t,ky[0],t,ky[1],t+d,ky[0],t+d,ky[1]);
}
}
if(!P(cx[x[i]])&&(cx[x[i]]||tx^2)) !cx[x[i]]&&(kx[tx++]=x[i]),cx[x[i]]^=1<<i,dfs(i+1,tx,ty),!(cx[x[i]]^=1<<i)&&--tx;//不能有超过两条重合,同向不能超过两条不同
if(!P(cy[y[i]])&&(cy[y[i]]||ty^2)) !cy[y[i]]&&(ky[ty++]=y[i]),cy[y[i]]^=1<<i,dfs(i+1,tx,ty),!(cy[y[i]]^=1<<i)&&--ty;//不能有超过两条重合,同向不能超过两条不同
}
int main()
{
RI Tt,i;for(scanf("%d",&Tt),i=0;i^4;++i) id[1<<i]=i;W(Tt--)
{
for(i=0;i^4;++i) scanf("%d%d",x+i,y+i);if(ans=1e9,dfs(0),ans==1e9) {puts("-1");continue;};
for(printf("%d\n",ans),i=0;i^4;++i) printf("%d %d\n",sx[i],sy[i]);
}return 0;
}
待到再迷茫时回头望,所有脚印会发出光芒