P3438 [POI2006]ZAB-Frogs

· · 题解

P3438 [POI2006]ZAB-Frogs

在我的 cnblogs 中查看。

给出一个不一样的解法。不需要用到斜率优化等高级算法。

下文记 n=w_x,m=w_y

首先,答案显然满足可二分性,因此二分答案 d\in [0,nm] 确定距离的平方。

这样我们将题目转化为:求起点到终点之间是否有一条路径使得任何一个点都不被圆心是整点且半径相同的若干个圆所覆盖。记 r=\sqrt d。如果处理出一个点是否被覆盖,那么可以 \mathcal{O}(nm) BFS 求出答案

枚举每一竖列即 x,显然覆盖到该列的圆的圆心 x_i 坐标满足 (x_i-x)^2\leq dx_i-r\leq x\leq x_i+r。将所有圆按照 x_i 从小到大排序,Two-pointers 维护即可。

如果直接考虑一个点被覆盖的圆会很难做。不妨转换思路,枚举能够覆盖这一竖列的所有圆,记圆心为 (x_i,y_i)\ (x_i-r\leq x\leq x_i+r)。显然,它能覆盖到的第 x 列的所有点的纵坐标 y 形成了一段区间。差分计算线段覆盖即可。

当然这么做显然是过不去的,因为一共有 m 列,而枚举到的圆的个数为 \mathcal{O}(nm)。因此时间复杂度为 nm^2\log(nm)

考虑优化上面的算法。注意到在枚举所有圆时,如果出现两个圆 (x_1,y_1)(x_2,y_2) 满足 y_1=y_2|x_1-x|>|x_2-x|,那么我们根本不需要考虑第一个圆 (x_1,y_1),因为它能覆盖到的第 x 列的所有点包含于 (x_2,y_2) 能覆盖到的第 x 列的所有点。这是很显然的,因为前者的 y 需要满足 |y-y_1|\leq \sqrt {d-(x_1-x)^2},后者的 y 需要满足 |y-y_2|\leq \sqrt {d-(x_2-x)^2},而 \sqrt {d-(x_1-x)^2}>\sqrt {d-(x_2-x)^2}

因此,对于每一行 y,维护圆心纵坐标为 y 且与当前 x 距离最小的圆,那么最终我们只需枚举 \mathcal{O}(n) 个圆即可。具体地,维护 n 个存横坐标 x_i 的队列,在 Two-pointers 添加或删除一个圆 (x_i,y_i) 时,对第 y_i 个队列进行添加或删除操作即可。此外,移动到下一列 x\gets x+1 时,不断弹出每个队的队首元素直至队列只剩下一个元素或队列队首横坐标与 x+1 的距离小于队首下一个横坐标与 x+1 的距离。

时间复杂度为 \mathcal{O}(nm\log (nm))

#include <bits/stdc++.h>
using namespace std;

#define gc getchar()
#define pii pair <int,int>
#define fi first
#define se second
#define mem(x,v) memset(x,v,sizeof(x))

inline int read(){
    int x=0,sign=0; char s=gc;
    while(!isdigit(s))sign|=s=='-',s=gc;
    while(isdigit(s))x=(x<<1)+(x<<3)+(s-'0'),s=gc;
    return sign?-x:x;
}

const int N=1e3+5;

int n,m,leg[N][N];
int stx,sty,edx,edy,num;
struct point{
    int x,y;
}p[N*N],mp[N][N];

bool vis[N][N];
pii q[N*N];
bool bfs(){
    if(vis[stx][sty]==1)return 0;
    int h=1,tl=0; q[++tl]={stx,sty},vis[stx][sty]=1;
    while(h<=tl){
        int x=q[h].fi,y=q[h++].se;
        if(x==edx&&y==edy)return 1;
        if(!vis[x+1][y])vis[x+1][y]=1,q[++tl]={x+1,y};
        if(!vis[x-1][y])vis[x-1][y]=1,q[++tl]={x-1,y};
        if(!vis[x][y+1])vis[x][y+1]=1,q[++tl]={x,y+1};
        if(!vis[x][y-1])vis[x][y-1]=1,q[++tl]={x,y-1};
    } return 0;
}

int dist(int a,int b,int c,int d){return (a-c)*(a-c)+(b-d)*(b-d);}

int d[N][N],hd[N],len[N],s[N];
void add(point x){d[x.y][len[x.y]++]=x.x;}
void del(point x){if(d[x.y][hd[x.y]]==x.x)hd[x.y]++;}
void update(int y,int x){while(d[y][hd[y]+1]&&abs(d[y][hd[y]]-x)>=abs(d[y][hd[y]+1]-x))hd[y]++;}

bool check(int x){
    int dis=sqrt(x);
    mem(vis,0),mem(d,0),mem(hd,0),mem(len,0);
    for(int i=0;i<=max(n,m);i++)vis[0][i]=vis[i][0]=vis[n+1][i]=vis[i][m+1]=1;
    for(int i=1,l=1,r=1;i<=n;i++,mem(s,0)){
        while(r<=num&&i+dis>=p[r].x)add(p[r++]);
        while(l<=num&&i-dis>p[l].x)del(p[l++]);
        for(int j=1;j<=m;j++){
            update(j,i); int xx=d[j][hd[j]];
            if(!xx)continue;
            int radius=sqrt(x-(xx-i)*(xx-i)),l=j-radius,r=j+radius+1;
            s[max(1,l)]++,s[min(m+1,r)]--; 
        } for(int j=1;j<=m;j++)vis[i][j]=(s[j]+=s[j-1])>0;
    } return bfs();
}

int main(){
    cin>>n>>m>>stx>>sty>>edx>>edy>>num;
    for(int i=1;i<=num;i++)p[i].x=read(),p[i].y=read(),mp[p[i].x][p[i].y]=p[i];
    for(int i=1,cnt=0;i<=n;i++)for(int j=1;j<=m;j++)if(mp[i][j].x!=0)p[++cnt]=mp[i][j];
    int l=-1,r=2e6;
    while(l<r){
        int m=(l+r>>1)+1;
        check(m)?l=m:r=m-1;
    } cout<<l+1<<endl;
    return 0;
}