题意
给出一个正方形区域,其中有一些点是障碍。每次询问给出两个球的初始位置,询问至少需要改变几次重力才能使两球相遇。
解析
首先我们发现,实际上仅有障碍周围(包括地图边界)上的点是有意义的(因为任何位置的球经过一次操作一定会到这些点,显然一次操作是可以枚举的)。我们定义每一组有意义的
我们可以考虑先将所有有意义的点对
inline bool check(int i,int j){ if(a[i-1][j]||a[i+1][j]||a[i][j-1]||a[i][j+1]) return true; return false; } …… scanf("%d%d%d",&n,&m,&Q); for(int i=1,x,y;i<=m;i++){ scanf("%d%d",&x,&y); a[x][y]=1;//标记障碍 } for(int i=1;i<=n;i++) a[0][i]=a[n+1][i]=a[i][0]=a[i][n+1]=1;//标记障碍 for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) if(!a[i][j]&&check(i,j)) id[i][j]=++tot;//对有意义的点进行编号 for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) t[i][j][0]=a[i][j-1]?id[i][j]:t[i][j-1][0], t[i][j][1]=a[i-1][j]?id[i][j]:t[i-1][j][1]; for(int i=n;i;i--) for(int j=n;j;j--) t[i][j][2]=a[i][j+1]?id[i][j]:t[i][j+1][2], t[i][j][3]=a[i+1][j]?id[i][j]:t[i+1][j][3]; //分别处理出从 (i,j) 向各个方向走所到达的点的状态号
接着我们发现两个小球最终相遇,相当于是两个球所在的点编号相同了,因此我们可以考虑从终点开始逆推。那么怎么转移状态呢?我们可以将每一个状态看作一个点,如果状态
inline void add(int u,int v,int p){ e[++cnt].to=v; e[cnt].nxt=head[u][p]; head[u][p]=cnt;//每个点的四种状态都需要一个 head,因为转移时需要枚举方向 } …… for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) if(id[i][j]) for(int k=0;k<=3;k++) add(t[i][j][k],id[i][j],k);//建反边
接着,我们定义
queue<pair<int,int> > q; for(int i=1;i<=tot;i++) for(int j=1;j<=tot;j++) dis[i][j]=1e9+7; for(int i=1;i<=tot;i++) q.push(make_pair(i,i)),dis[i][i]=1;//赋初值并 push 进队列 while(!q.empty()){ pair<int,int> x=q.front();q.pop(); for(int k=0;k<=3;k++){//枚举方向 for(int i=head[x.first][k];i;i=e[i].nxt){ for(int j=head[x.second][k];j;j=e[j].nxt){//枚举到达的状态 int u=e[i].to,v=e[j].to; if(dis[u][v]==1e9+7){ dis[u][v]=dis[x.first][x.second]+1, q.push(make_pair(u,v));//更新并 push 进队列 } } } } }
最后对于每一次询问,如果两个点重合输出
inline int getans(int X1,int Y1,int X2,int Y2){ return min(dis[t[X1][Y1][0]][t[X2][Y2][0]],min(dis[t[X1][Y1][1]][t[X2][Y2][1]],min(dis[t[X1][Y1][2]][t[X2][Y2][2]],dis[t[X1][Y1][3]][t[X2][Y2][3]]))); } …… for(int i=1,X1,X2,Y1,Y2;i<=Q;i++){ scanf("%d%d%d%d",&X1,&Y1,&X2,&Y2); if(X1==X2&&Y1==Y2) printf("0\n");//重合 else{ int ans=getans(X1,Y1,X2,Y2); if(ans>=1e9) printf("-1\n");//无解 else printf("%d\n",ans);//有解 } }
至此,这道题就被我们解决了。化点为状态,以及在状态之间进行连边以便于转移的操作非常巧妙,值得思考。
代码
#include<bits/stdc++.h> using namespace std; const int N=305,M=2005,MN=1e5+5; struct edge{ int to,nxt; }e[MN]; int a[N][N],dis[M][M],id[N][N],t[M][M][4]; int n,m,Q,cnt,head[M][4],tot; inline void add(int u,int v,int p){ e[++cnt].to=v; e[cnt].nxt=head[u][p]; head[u][p]=cnt; } inline bool check(int i,int j){ if(a[i-1][j]||a[i+1][j]||a[i][j-1]||a[i][j+1]) return true; return false; } inline int getans(int X1,int Y1,int X2,int Y2){ return min(dis[t[X1][Y1][0]][t[X2][Y2][0]],min(dis[t[X1][Y1][1]][t[X2][Y2][1]],min(dis[t[X1][Y1][2]][t[X2][Y2][2]],dis[t[X1][Y1][3]][t[X2][Y2][3]]))); } queue<pair<int,int> > q; int main(){ scanf("%d%d%d",&n,&m,&Q); for(int i=1,x,y;i<=m;i++){ scanf("%d%d",&x,&y); a[x][y]=1; } for(int i=1;i<=n;i++) a[0][i]=a[n+1][i]=a[i][0]=a[i][n+1]=1; for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) if(!a[i][j]&&check(i,j)) id[i][j]=++tot; for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) t[i][j][0]=a[i][j-1]?id[i][j]:t[i][j-1][0], t[i][j][1]=a[i-1][j]?id[i][j]:t[i-1][j][1]; for(int i=n;i;i--) for(int j=n;j;j--) t[i][j][2]=a[i][j+1]?id[i][j]:t[i][j+1][2], t[i][j][3]=a[i+1][j]?id[i][j]:t[i+1][j][3]; for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) if(id[i][j]) for(int k=0;k<=3;k++) add(t[i][j][k],id[i][j],k); for(int i=1;i<=tot;i++) for(int j=1;j<=tot;j++) dis[i][j]=1e9+7; for(int i=1;i<=tot;i++) q.push(make_pair(i,i)),dis[i][i]=1; while(!q.empty()){ pair<int,int> x=q.front();q.pop(); for(int k=0;k<=3;k++){ for(int i=head[x.first][k];i;i=e[i].nxt){ for(int j=head[x.second][k];j;j=e[j].nxt){ int u=e[i].to,v=e[j].to; if(dis[u][v]==1e9+7){ dis[u][v]=dis[x.first][x.second]+1, q.push(make_pair(u,v)); } } } } } for(int i=1,X1,X2,Y1,Y2;i<=Q;i++){ scanf("%d%d%d%d",&X1,&Y1,&X2,&Y2); if(X1==X2&&Y1==Y2) printf("0\n"); else{ int ans=getans(X1,Y1,X2,Y2); if(ans>=1e9) printf("-1\n"); else printf("%d\n",ans); } } return 0; }
Orz大佬讲的很好)
发现 N 月前啥都不会的自己(
鸭,主题换了?
yes