把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

【洛谷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;
}
posted @ 2021-05-31 16:34  TheLostWeak  阅读(57)  评论(0编辑  收藏  举报