题目传送门:https://ac.nowcoder.com/acm/contest/549/G

思路

把起点和终点放进同一个队列(实际上是两个队列),如果从C(或D)走过某个点并且这个点已经被D(或C)访问过了,说明他们能相遇,此时的步数即为答案。本题关键就在于某个人要走的下一个点已经被对方走过了,此时结束搜索,返回答案。

AC代码

#include <bits/stdc++.h>
using namespace std;
const int N=1010;
char s[N][N];
int n,m,bx,by,ex,ey,vis1[N][N],vis2[N][N];
int dir[8][2]={0,1,0,-1,1,0,-1,0,1,1,-1,-1,1,-1,-1,1};
struct node
{
    int id,x,y,cnt;//id表示是哪一个人的队列
};
queue<node>q;
int bfs()
{
    q.push({1,bx,by,0});
    vis1[bx][by]=1;
    q.push({2,ex,ey,0});
    vis2[ex][ey]=1;
    while(!q.empty())
    {
        node tmp=q.front();q.pop();
        int x=tmp.x,y=tmp.y,cnt=tmp.cnt,id=tmp.id;
        if(id==1)
        {
            for(int i=0;i<8;i++)
            {
                int nx=x+dir[i][0];
                int ny=y+dir[i][1];
                if(nx>=1&&nx<=n&&ny>=1&&ny<=m&&s[nx][ny]!='#'&&!vis1[nx][ny])
                {
                    if(vis2[nx][ny])return cnt+1;
                    q.push({1,nx,ny,cnt+1});
                    vis1[nx][ny]=1;
                }
            }
        }
        else
        {
            for(int i=0;i<4;i++)
            {
                int nx=x+dir[i][0];
                int ny=y+dir[i][1];
                if(nx>=1&&nx<=n&&ny>=1&&ny<=m&&s[nx][ny]!='#'&&!vis2[nx][ny])
                {
                    if(vis1[nx][ny])return cnt+1;
                    q.push({2,nx,ny,cnt+1});
                    vis2[nx][ny]=1;
                    for(int i=0;i<4;i++)
                    {
                        int nnx=nx+dir[i][0];
                        int nny=ny+dir[i][1];
                        if(nnx>=1&&nnx<=n&&nny>=1&&nny<=m&&s[nnx][nny]!='#'&&!vis2[nnx][nny])
                        {
                            if(vis1[nnx][nny])return cnt+1;
                            q.push({2,nnx,nny,cnt+1});
                            vis2[nnx][nny]=1;
                        }
                    }
                }
            }
        }
    }
    return -1;
}
int main()
{
    ios::sync_with_stdio(false);
    cin>>n>>m;
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
        {
            cin>>s[i][j];
            if(s[i][j]=='C')bx=i,by=j;
            else if(s[i][j]=='D')ex=i,ey=j;
        }
    int ans=bfs();
    if(ans!=-1)printf("YES\n%d\n",ans);
    else printf("NO\n");
    return 0;
}

AC代码简化

可以简化一下代码,把bfs函数分成两个函数。
然后把两个vis数组合并,写成三维数组,第一个下标表示标记的是谁的路径。
把队列写成一维数组,下标表示是谁的队列。假设队列下标是u,用u^1表示u异或1,即对u取反(u=0则u\^1=1,u=1则u\^1=0),这样方便得到对方的队列下标。

#include <bits/stdc++.h>
using namespace std;
const int N=1010;
char s[N][N];
int n,m,bx,by,ex,ey,vis[2][N][N];
int dir[8][2]={0,1,0,-1,1,0,-1,0,1,1,-1,-1,1,-1,-1,1};
struct node
{
    int x,y;
};
queue<node>q[2];
bool bfs(int u)//u表示队列下标
//这个函数如果返回1,表示已经找到答案,结束搜索;否则继续搜索
{
    int sz=q[u].size();//先记录当前队列中元素个数,之后只对这一层进行扩展搜索
    while(sz--)//这里一定要注意不是while(!q.empty()),否则只会对一个队列进行不断的广搜直到无法搜索
    {
        node tmp=q[u].front();q[u].pop();
        int x=tmp.x,y=tmp.y;
        for(int i=0;i<(u==0?8:4);i++)
        {
            int nx=x+dir[i][0];
            int ny=y+dir[i][1];
            if(nx>=1&&nx<=n&&ny>=1&&ny<=m&&s[nx][ny]!='#'&&!vis[u][nx][ny])
            {
                if(vis[u^1][nx][ny])return 1;//u^1即u异或1,表示对u取反;
                //如果下一个点被对方走过,直接返回1,找到答案,结束搜索
                q[u].push({nx,ny});
                vis[u][nx][ny]=1;
            }
        }
    }
    return 0;
}
int solve()
{
    q[0].push({bx,by});
    vis[0][bx][by]=1;
    q[1].push({ex,ey});
    vis[1][ex][ey]=1;
    int cnt=0;
    while(!q[0].empty()||!q[1].empty())
    {
        cnt++;//一层一层扩展,记录步数
        if(bfs(0))return cnt;
        if(bfs(1))return cnt;
        if(bfs(1))return cnt;//队列下标为1的人可以走两次
    }
    return -1;
}
int main()
{
    ios::sync_with_stdio(false);
    cin>>n>>m;
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
        {
            cin>>s[i][j];
            if(s[i][j]=='C')bx=i,by=j;
            else if(s[i][j]=='D')ex=i,ey=j;
        }
    int ans=solve();
    if(ans!=-1)printf("YES\n%d\n",ans);
    else printf("NO\n");
    return 0;
}