1. 程式人生 > 其它 >ZJNU 2018 - Portal (bfs)

ZJNU 2018 - Portal (bfs)

ZJNU 2018 - Portal

題面

一個人要從圖中的\(C\)點走到\(F\)​​點。

每一秒他可以往上下左右四個方向走一步,只要目標點不是牆'#'。

另外,他可以通過神奇的槍在牆上開兩個傳送門,槍可以射中上下左右四個方向第一次遇到的牆;如果他站在有傳送門的牆邊,他就可以花一秒穿越傳送門到另外一個傳送門面對的點上。

射擊產生傳送門所花費的時間不計。

傳送門同一時間最多存在兩個。一旦他射擊產生了第三個傳送門,那麼第一個傳送門將會自動消失。

問從點\(C\)走到點\(F\)的最短時間。


思路

考慮直接BFS搜尋

除了常規四個方向外,本題還可以使用傳送門進行傳送

所以可以預處理出某一點\(P\)

​向四個方向射擊能產生傳送門的位置\(Q\)​(即某一方向最近的牆的位置),那麼從點\(P\)​​通過傳送門來到牆邊這一點\(Q\)​​所需要花費的時間也就是距離點\(P\)​​​最近的牆的距離\(+1\)

對於上下左右四個方向最近牆的位置,\(O(n^2)\)預處理即可

而對於某個點最近的牆的距離,以牆為起點先搜尋一遍整張圖,處理完後再BFS起點到終點計算答案


#include<bits/stdc++.h>
#define closeSync ios::sync_with_stdio(0);cin.tie(0);cout.tie(0)
#define multiCase int T;cin>>T;for(int t=1;t<=T;t++)
#define rep(i,a,b) for(int i=(a);i<=(b);i++)
#define repp(i,a,b) for(int i=(a);i<(b);i++)
#define per(i,a,b) for(int i=(a);i>=(b);i--)
#define perr(i,a,b) for(int i=(a);i>(b);i--)
#define all(a) (a).begin(),(a).end()
#define SUM(a) accumulate(all(a),0LL)
#define MIN(a) (*min_element(all(a)))
#define MAX(a) (*max_element(all(a)))
#define mst(a,b) memset(a,b,sizeof(a))
#define pb push_back
#define eb emplace_back
#define fi first
#define se second
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pii;
const int INF=0x3f3f3f3f;
const ll LINF=0x3f3f3f3f3f3f3f3f;
const double eps=1e-12;
const double PI=acos(-1.0);
const ll mod=998244353;
const int dx[8]={0,1,0,-1,1,1,-1,-1},dy[8]={1,0,-1,0,1,-1,1,-1};
void debug(){cerr<<'\n';}template<typename T,typename... Args>void debug(T x,Args... args){cerr<<"[ "<<x<< " ] , ";debug(args...);}
mt19937 mt19937random(std::chrono::system_clock::now().time_since_epoch().count());
ll getRandom(ll l,ll r){return uniform_int_distribution<ll>(l,r)(mt19937random);}
ll gcd(ll a,ll b){return b==0?a:gcd(b,a%b);}
ll qmul(ll a,ll b){ll r=0;while(b){if(b&1)r=(r+a)%mod;b>>=1;a=(a+a)%mod;}return r;}
ll qpow(ll a,ll n){ll r=1;while(n){if(n&1)r=(r*a)%mod;n>>=1;a=(a*a)%mod;}return r;}
ll qpow(ll a,ll n,ll p){ll r=1;while(n){if(n&1)r=(r*a)%p;n>>=1;a=(a*a)%p;}return r;}

int n,m;
char mp[505][505];
int sx,sy,tx,ty;
int lef[505][505],rig[505][505],top[505][505],btm[505][505];
int dis[505][505],rdis[505][505];

inline bool prim(int x,int y)
{
    return x>0&&y>0&&x<=n&&y<=m;
}

void solve()
{
    rep(i,1,n)
        cin>>(mp[i]+1);
        
    queue<pii> q;
    mst(dis,INF);
    mst(rdis,INF); //某點最近的牆的距離
    
    rep(i,1,n)
        rep(j,1,m)
        {
            if(mp[i][j]=='C')
                sx=i,sy=j;
            else if(mp[i][j]=='F')
                tx=i,ty=j;
            else if(mp[i][j]=='#')
            {
                repp(k,0,4)
                {
                    int x=dx[k]+i,y=dy[k]+j;
                    if(rdis[x][y]==0)
                        continue;
                    if(prim(x,y)&&mp[x][y]!='#')
                        q.push(pii(x,y)),rdis[x][y]=0;
                }
            }
        }
    
    while(!q.empty()) //先預處理rdis
    {
        int x=q.front().fi,y=q.front().se;
        q.pop();
        repp(i,0,4)
        {
            int px=x+dx[i],py=y+dy[i];
            if(prim(px,py)&&mp[px][py]!='#')
            {
                if(rdis[px][py]>rdis[x][y]+1)
                {
                    rdis[px][py]=rdis[x][y]+1;
                    q.push(pii(px,py));
                }
            }
        }
    }
    
    rep(i,1,n) //處理四個方向上傳送門能夠到達的點
    {
        int t=0;
        rep(j,1,m)
        {
            if(mp[i][j]=='#')
            {
                t=j;
                continue;
            }
            lef[i][j]=t+1;
        }
        t=n+1;
        per(j,m,1)
        {
            if(mp[i][j]=='#')
            {
                t=j;
                continue;
            }
            rig[i][j]=t-1;
        }
    }
    rep(j,1,m)
    {
        int t=0;
        rep(i,1,n)
        {
            if(mp[i][j]=='#')
            {
                t=i;
                continue;
            }
            top[i][j]=t+1;
        }
        t=n+1;
        per(i,n,1)
        {
            if(mp[i][j]=='#')
            {
                t=i;
                continue;
            }
            btm[i][j]=t-1;
        }
    }
    
    q.push(pii(sx,sy));
    dis[sx][sy]=0;
    
    while(!q.empty())
    {
        int x=q.front().fi,y=q.front().se;
        q.pop();
        repp(i,0,4) //普通的四個方向
        {
            int px=x+dx[i],py=y+dy[i];
            if(prim(px,py)&&mp[px][py]!='#')
            {
                if(dis[px][py]>dis[x][y]+1)
                {
                    dis[px][py]=dis[x][y]+1;
                    q.push(pii(px,py));
                }
            }
        }
        int px,py;
        px=x,py=lef[x][y]; //向左射擊產生傳送門能到達的點
        if(prim(px,py)&&mp[px][py]!='#')
        {
            if(dis[px][py]>dis[x][y]+rdis[x][y]+1)
            {
                dis[px][py]=dis[x][y]+rdis[x][y]+1;
                q.push(pii(px,py));
            }
        }
        px=x,py=rig[x][y];
        if(prim(px,py)&&mp[px][py]!='#')
        {
            if(dis[px][py]>dis[x][y]+rdis[x][y]+1)
            {
                dis[px][py]=dis[x][y]+rdis[x][y]+1;
                q.push(pii(px,py));
            }
        }
        px=top[x][y],py=y;
        if(prim(px,py)&&mp[px][py]!='#')
        {
            if(dis[px][py]>dis[x][y]+rdis[x][y]+1)
            {
                dis[px][py]=dis[x][y]+rdis[x][y]+1;
                q.push(pii(px,py));
            }
        }
        px=btm[x][y],py=y;
        if(prim(px,py)&&mp[px][py]!='#')
        {
            if(dis[px][py]>dis[x][y]+rdis[x][y]+1)
            {
                dis[px][py]=dis[x][y]+rdis[x][y]+1;
                q.push(pii(px,py));
            }
        }
    }
    if(dis[tx][ty]==INF)
        cout<<"nemoguce\n";
    else
        cout<<dis[tx][ty]<<'\n';
}
int main()
{
    closeSync;
    while(cin>>n>>m)
    {
        solve();
    }
    return 0;
}