P10137 [USACO24JAN] Walking in Manhattan G 题解

有一个二维平面,上面有 \(n\) 条横线和竖线,有 \(q\) 个机器人,初始位置给定,每秒沿原来的方向移动 \(1\) 单位,遇到交叉点当前时间是偶数就转到竖线,否则转到横线,求每个机器人 \(d_i\) 秒后的位置。

暴力做法就是对每个机器人模拟,复杂度显然爆炸了,我们研究一下怎么快速跳过去。假设机器人起点是 \((x_0,y_0)\),走到了 \((X,Y)\) 这个岔口,这段路的时间 \(t\) 满足 t=X-x_0+Y-y_0 即曼哈顿距离(这或许是这个题叫"漫步曼哈顿"的原因)。根据题意转向与否只与 \(t\) 有关,那么我们定义机器人的特征值 \(P=(x_0+y_0)\bmod 2\),将 \(t\) 代入得到 t=(X+Y)-(x_0+y_0)\\equiv X+Y-P(\\bmod 2) 这说明机器人在路口时时间由路口坐标和 \(X+Y\) 以及它本身的特征值决定。那么转向规则就变成了:

  • 转成竖线:\(t\equiv 0(\bmod 2)\Longrightarrow X+Y\equiv P(\bmod 2)\)
  • 转成横线:\(t\equiv 1(\bmod 2)\Longrightarrow X+Y\not\equiv P(\bmod 2)\)

下一步非常关键,假设现在在 \(X_k\) 的竖线上,路口横线为 \(Y_k\),如果转向必须满足 X_k+Y_k\\not\\equiv P(\\bmod 2) 接着在 \(Y_k\) 的横线上,下一个路口竖线是 \(X_{k+1}\),如果转向必须满足 X_{k+1}+Y_k\\equiv P(\\bmod 2) 联立后可消掉 \(P\) 和 \(Y_k\) 得到 X_k\\not\\equiv X_{k+1}(\\bmod 2) 同理可得 \(Y_k\not\equiv Y_{k+1}(\bmod 2)\)。这说明只要转弯,道路编号就会翻转,不停地交替下去。

基于这个性质,如果机器人目前在 \(X_i\) 的岔路转弯了,那么下一次重新回到竖线方向一定在第一个与 \(X_i\) 奇偶性不同的 \(X_j\) 上。这相当于把一个二维平面转成横竖线两种独立的模式。换句话说,从 \(X_k\) 出发,下一次转回来的点只依赖于 \(X_k\)。所以我们可以使用倍增来加速模拟的过程。

倍增预处理要开两个数组,因为横竖是独立的。\(f_{i,0}\) 表示从 \(i\) 出发第一条奇偶性不同的路,这里可以 \(O(n)\) 算一下。后面就是经典的转移。而查询每个机器人的位置分为三部分:

  • 首先,机器人不一定一开始就在路口,先二分到第一个路口。如果 \(d_i\) 都不够走到路口直接返回
  • 接下来最关键的,从大到小枚举试跳,更新坐标,不够了也直接返回
  • 最后还剩一段,这里不能再拐两次了,最多拐一次弯,分类讨论一下

最终时间复杂度 \(O((n+q)\log n)\)。

给一下实现,感觉还是很清晰(没有注释)。

cpp 复制代码
#include<bits/stdc++.h>
#define L(a,b,c,d) for(int a=b;a<=c;a+=d)
#define R(a,b,c,d) for(int a=b;a>=c;a-=d)

using namespace std;
typedef long long i64;
typedef __int128 i128;

const int N=2e5+5,LOG=20;

void solve();
int n,q;
int fx[N][LOG],fy[N][LOG];
vector<int> X,Y;

signed main(){
  int Test=1;
//  scanf("%d",&Test);
  while(Test--) solve();
  return 0;
}

void build(vector<int>& a,int f[][LOG]){
  int len=a.size();
  L(i,0,LOG-1,1) f[len][i]=len;
  R(i,len-1,0,1){
    if((a[i]%2)!=(a[i+1]%2)) f[i][0]=i+1;
    else f[i][0]=f[i+1][0];
    L(j,1,LOG-1,1) f[i][j]=f[f[i][j-1]][j-1];
  }
}

pair<int,int> query(int x,int y,int d){
  int xi=lower_bound(X.begin(),X.end(),y)-X.begin(),
  yi=lower_bound(Y.begin(),Y.end(),x)-Y.begin();
  int t=0;
  if(y<X[xi]){
    int dist=X[xi]-y;
    if(dist>d) return {x,y+d};
    d-=dist;
    t+=dist;
    y=X[xi];
  }
  if(x<Y[yi]){
    int dist=Y[yi]-x;
    if(dist>d) return {x+d,y};
    d-=dist;
    t+=dist;
    x=Y[yi];
  }
  R(j,LOG-1,0,1){
    int nx=fx[xi][j],ny=fy[yi][j];
    if(nx==X.size()||ny==Y.size()) continue;
    int dx=Y[ny]-Y[yi],dy=X[nx]-X[xi];
    if(dx>d||dy>d) continue;
    int dist=dx+dy;
    if(dist>d) continue;
    d-=dist;
    t+=dist;
    xi=nx,yi=ny;
    x=Y[yi],y=X[xi];
  }
  if(t%2){
    int ny=fy[yi][0];
    if(ny==Y.size()) return {x+d,y};
    int dx=Y[ny]-Y[yi];
    if(dx>=d) return {x+d,y};
    d-=dx;
    x=Y[ny];
    return {x,y+d};
  }
  else{
    int nx=fx[xi][0];
    if(nx==X.size()) return {x,y+d};
    int dy=X[nx]-X[xi];
    if(dy>=d) return {x,y+d};
    d-=dy;
    y=X[nx];
    return {x+d,y};
  }
}

void solve(){
  scanf("%d%d",&n,&q);
  L(i,0,n-1,1){
    char t[3];
    int c;
    scanf("%s%d",&t,&c);
    if(t[0]=='H') X.push_back(c);
    else Y.push_back(c);
  }
  sort(X.begin(),X.end());
  sort(Y.begin(),Y.end());
  X.push_back(2e9);
  Y.push_back(2e9);
  build(X,fx);
  build(Y,fy);
  while(q--){
    int x,y,d;
    scanf("%d%d%d",&x,&y,&d);
    auto [ansx,ansy]=query(x,y,d);
    printf("%d %d\n",ansx,ansy);
  }
}