【洛谷3438】[POI2006] ZAB-Frogs(斜率优化)
- 给定一张\(n\times m\)的网格图,其中有\(t\)个关键点。
- 求一条从\((sx,sy)\)到\((ex,ey)\)的路径,使得离所有关键点的最小欧几里得距离最大。
- \(n,m\le10^3,t\le n\times m\)
斜率优化
考虑设\(f_{i,j}\)表示\((i,j)\)离所有关键点的最小欧几里得距离。
容易写出转移式:
\[\begin{align}
f_{i,j}&=\max\{(i-x_k)^2+(j-y_k)^2\}\\
&=\max\{-2ix_k-2jy_k+x_k^2+y_k^2\}+i^2+j^2
\end{align}
\]
发现这是一个斜率优化的形式,虽然是二元,但却是相互独立的。
我们从小到大枚举行\(i\),然后先考虑每一列在第\(i\)行的最优点,比较\(p,q\)(\(x_p<x_q\)):
\[-2ix_p+x_p^2<-2ix_q+x_q^2\\
2i<x_p+x_q
\]
发现同列的最优决策点是单调移动的,因此我们可以很方便地求出每一列的决策点,这样一来对于某一行有用的决策点个数就是\(O(m)\)的了。
方便起见,令\(v_k=-2ix_k+x_k^2+y_k^2\),然后对于第\(i\)行比较\(p,q\)(\(y_p<y_q\)):
\[-2jy_p+v_p<-2jy_q+v_q\\
2j<\frac{v_q-v_p}{y_q-y_p}
\]
因此,我们先取出每一列的决策点建一个斜率单调递增的单调队列,然后枚举\(j\)求出答案即可。
枚举答案+并查集合并
根据\(f_{i,j}\)把\((i,j)\)扔到对应的桶里。
从大到小枚举答案,每次把桶中的所有位置取出,尝试与四周的方格在并查集上合并。
当起点和终点在一个连通块中了,此时枚举到的就是答案。
注意特判起点和终点重合的情况。
代码:\(O(nm\alpha(nm))\)
#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
#define N 1000
#define ID(x,y) (((x)-1)*m+(y))
using namespace std;
const int dx[4]={-1,1,0,0},dy[4]={0,0,-1,1};
int n,m,sx,sy,ex,ey,qe[N+5][N+5],nw[N+5],ct[N+5],v[N+5],q[N+5],f[N+5][N+5];
vector<pair<int,int> > V[N*N+5];vector<pair<int,int> >::iterator it;
namespace U//并查集合并
{
int f[N*N+5];I int fa(CI x) {return f[x]?f[x]=fa(f[x]):x;}
I void M(RI x,RI y) {(x=fa(x))^(y=fa(y))&&(f[x]=y);}//合并
I bool C(CI x,CI y) {return fa(x)==fa(y);}//判连通
}
int main()
{
RI i,j,x,y,t;scanf("%d%d%d%d%d%d",&n,&m,&sx,&sy,&ex,&ey);
for(scanf("%d",&t);t;--t) scanf("%d%d",&x,&y),qe[y][++ct[y]]=x;
for(i=1;i<=m;++i) sort(qe[i]+1,qe[i]+ct[i]+1),nw[i]=1;//每一列的关键点按行号排序
RI H,T;for(i=1;i<=n;++i)
{
for(H=1,T=0,j=1;j<=m;++j) if(ct[j])
{
W(nw[j]^ct[j]&&2*i>=qe[j][nw[j]]+qe[j][nw[j]+1]) ++nw[j];//更新每一列的最优决策点
v[j]=-2*i*qe[j][nw[j]]+qe[j][nw[j]]*qe[j][nw[j]]+j*j;
W(H<T&&1LL*(v[q[T]]-v[q[T-1]])*(j-q[T])>=1LL*(v[j]-v[q[T]])*(q[T]-q[T-1])) --T;q[++T]=j;//维护斜率单调递增的单调队列
}
for(j=1;j<=m;++j)
{
W(H^T&&2LL*j*(q[H+1]-q[H])>=v[q[H+1]]-v[q[H]]) ++H;//弹出队首不优元素
V[f[i][j]=v[q[H]]-2*j*q[H]+i*i+j*j].push_back(make_pair(i,j));//从队首转移,扔至f[i][j]对应桶中
}
}
if(ID(sx,sy)==ID(ex,ey)) return printf("%d\n",f[sx][sy]),0;//特判起点与终点相同
RI k,nx,ny;for(i=n*m;~i;--i)//从大到小枚举答案
{
for(it=V[i].begin();it!=V[i].end();++it) for(k=ID(it->first,it->second),j=0;j^4;++j)
(nx=it->first+dx[j])&&nx<=n&&(ny=it->second+dy[j])&&ny<=m&&f[nx][ny]>=i&&(U::M(k,ID(nx,ny)),0);//尝试与四周合并
if(U::C(ID(sx,sy),ID(ex,ey))) {printf("%d\n",i);break;}//如果连通了,则当前枚到的就是答案
}return 0;
}
待到再迷茫时回头望,所有脚印会发出光芒