XDU1202(西电17年校赛F题)题解&关于spfa与dijstra+priority_queue的思考

探讨了在包含特殊传送区域的矩阵中寻找从起点到终点最短路径的方法。作者经历了从SPFA算法到Dijkstra算法的转变,并最终解决了问题。

题意

链接
给出一个矩阵,从起点到终点,可以一步步走过去,也可以在有命运石大门的区域选择使用steins:gate传送,求最短路径

笺释

这道题来来回回写了十几个版本,在xdoj上提交了不下20次,真是辛苦评测机了。
一开始其实想偏了,考虑到可以传送可以走路,以为类似于守望者的逃离,将传送与走路分开处理再合并。但是实际实现过程中发现可以处理出来只传送的情况,可以处理出来只走路的情况,但是合并的话,一部分走路,一部分传送这个状态很难表示。守望者的逃离之所以可以分开处理是因为她可以贪心选择能传送就一定传送,不会像这道题一样遇到难以合并的情况
然后就考虑用最直接的spfa算法求最短路了,从队列里拿到一个点就考虑从它向四个方向行走能到达的点,从他使用传送门能到达的点,然后这个算法tle了。
接着考虑能不能预处理出来从一个点用传送门能到达的点,这样就不用每拿到一个点都搜索有哪些传送门可用,直接将用传送门和走路抽象成等同的移动方式,每次拿到一个点之后遍历所有能到达的点。
这样遇到的问题就是开不出来这么大的数组表示任意两个点之间的距离,最大要开100*100*100*100=1e9个int,当然会mle,然后想到了可以用vector只处理能到达的两个点之间的距离,如果有多种方式能够到达的话取最小距离,但是最小距离的更新又成了问题,我们通过一种移动方式从A点到B点,想知道之前有没有标记过A点到B点,如果标记过就更新距离为最小值,如果没标记过就加入A点到B点,但是这个过程朴素的思路要查找之前有没有到过,这样以来复杂度会升高很多。于是想到用key数组标记之前有没有到过,如果到过就记录key中的数值表示之前存在了哪个下标。但是key本身又需要100*100*100*100的空间,这时候想到将key仅设置为2维数组记录当前已确定的起点A到其余点B的更新情况,每拿到一个新点A可以循环利用key数组,就只需要100*100的空间了。
后来跟zxy大佬讨论的时候发现这个预处理的过程其实华而不实,并不能降低spfa的很多复杂度,反而自身实现起来相当困难。

void makemaps()
{
        for(int x1=1;x1<=n;x1++)
        {
            for(int y1=1;y1<=m;y1++)
            {
                memset(key,-1,sizeof(key));
                for(int i=1;i<=r;i++)
                {
                    int ax=doors[i].ax;
                    int ay=doors[i].ay;
                    int by=doors[i].by;
                    int bx=doors[i].bx;
                    int t=doors[i].t;
                    int p=doors[i].p;
                    if(!(x1>=ax&&x1<=bx&&y1>=ay&&y1<=by))
                    {
                        continue;
                    }
                for(int x2=ax;x2<=bx;x2++)
                {
                    for(int y2=ay;y2<=by;y2++)
                    {
                        if(x1==x2&&y1==y2)
                        {
                            continue;
                        }
                        if(abs(h[x1][y1]-h[x2][y2])<=p)
                        {
                                if(key[x2][y2]!=-1)
                                {
                                    G[x1][y1][key[x2][y2]].w=min(t,G[x1][y1][key[x2][y2]].w);
                                }
                                else
                                {
                                    G[x1][y1].push_back((edge){x2,y2,t});
                                    key[x2][y2]=G[x1][y1].size()-1;
                                }
                        }
                    }
                }
            }
            for(int k=1;k<=4;k++)
            {
                int x2=x1+movx[k];
                int y2=y1+movy[k];
                if(!legal(x2,y2))
                {
                    continue;
                }
                int nw=h[x1][x1]+h[x2][y2];
                if(key[x2][y2]!=-1)
                {
                    G[x1][y1][key[x2][y2]].w=min(h[x1][y1]+h[x2][y2],G[x1][y1][key[x2][y2]].w);
                }
                else
                {
                    G[x1][y1].push_back((edge){x2,y2,h[x1][y1]+h[x2][y2]});
                    key[x2][y2]=G[x1][y1].size()-1;
                }
            }
        }
    }
}

所以spfa也是tle。
有种逃脱不了命运之手的感觉呢。
然后zxy提供了dijstra+priority_queue的思路,看了一下对于稠密图来说dijstra+queue会比spfa快上不少,于是学习了一下dijstra的基本思想。
dijstra的基本操作是

dis[i]表示从起点到i点的最小距离,然后一开始把起点放入优先队列P,这时候从起点到起点的最短距离一定是确定的0,也就是说,这个dis[s(起点)]不可能再以之后的任何形式完成更新。于是我们可以利用s点更新(松弛)从s点能到达的点。对于从起点更新到的其他点A1,A2,A3,A4并不一定是最短距离,这时候对从起点到他们的距离是一个暂时值T,这时候我们从这些已有暂时值T的点A1,A2,A3,A4中选取一个最小的B,那么从起点到B的最短距离T就由暂时值变成了确定值,这个变化可以这样疏解:**从起点S到B点如不通过这种方式(当前更新时所使用的)到达,那么就一定要使用其他方式到达,而使用其他方式到达B之外的点C的距离一定大于直接到达点B的(直接到达点B已经是目前最小值),更何况从C到B也需要一段距离。**我认为这是一种很精髓的dp思想,利用这个思想就可以逐渐更新起点到所有点的最小值。

关于priority_queue优化的dijstra,还有这么一点值得注意:一个点A在没出队之前是可以多次进队的,但是当他出队之后,起点到他的最短距离就由暂时的不确定值变为了确定值,他不可能再次进队,队中可能有之前对此进入的多个点A,这些点A在读到的时候直接continue。
然后用dijstra+priority_queue写了之后发现还是不能过,我以为是因为我采用的记录更新方式是二维的,也就是说我直接利用矩阵中的原始坐标x,y作为(区分出)(specialize)一个点与其他点的依据,而zxy采用的是将所有点按照x*m+y分配一个编号,他的所有数据结构都是一维的,而我的都是二维的,如果一维和二维有速度上的差异,可能会导致tle。
然后把自己的二维改成了一维抽象点记录,发现还是tle,最后一句句地对拍发现有一处问题。

                    if(dis[nx][ny]>(dis[tx][ty]+t)&&(abs(h[tx][ty]-h[nx][ny])<=pp))
                    {
                        dis[nx][ny]=dis[tx][ty]+t;
                        p.push((node){nx,ny,dis[nx][ny]});
                    }

这个语句块的意思是如果两个点能通过门到达(海拔相差小于pp)且能松弛成功,则松弛。
我之前写成

                if(abs(h[tx][ty]-h[nx][ny])<=pp)
                {
                    if(dis[nx][ny]>(dis[tx][ty]+t))
                    {
                        dis[nx][ny]=dis[tx][ty]+t;
                        p.push((node){nx,ny,dis[nx][ny]});
                    }
                }

将这一句改成上面的写法之后就AC了,大概是因为第一个dis[nx][ny]的条件是更难满足的,通过这一点做了一个有效的剪枝吧。
这样改了之后二维的写法也勉强跑完了。

完整代码

//一维写法
#include<bits/stdc++.h>
#define MAXN 105
#define INF 0x3f3f3f3f
using namespace std;
int n,m,r,sx,sy,ox,oy,t;
int h[MAXN][MAXN];
int done[MAXN*MAXN];
int dis[MAXN*MAXN];
struct door
{
    int ax,ay,bx,by,t,p;
}doors[11];
struct node
{
    long long to,h;
    bool operator<(const node &A) const
    {
        return h>A.h;
    }
};
vector<node>G[MAXN*MAXN];
void dijstra()
{
    priority_queue<node>p;
    memset(done,0,sizeof(done));
    memset(dis,0x3f,sizeof(dis));
    p.push((node){sx*m+sy,0});
    dis[sx*m+sy]=0;
    while(!p.empty())
    {
        node newnode=p.top();
        p.pop();
        int to=newnode.to;
        int imp=newnode.h;
        if(done[to])
        {
            continue;
        }
        done[to]=1;
   //     printf("%d %d\n",tx,ty);
        for(int i=1;i<=r;i++)
        {
        if(((to/m)>=doors[i].ax&&(to/m)<=doors[i].bx&&(to%m)>=doors[i].ay&&(to%m)<=doors[i].by))
        {

        for(int nx=doors[i].ax;nx<=doors[i].bx;nx++)
        {
            for(int ny=doors[i].ay;ny<=doors[i].by;ny++)
            {
                    if(dis[nx*m+ny]>dis[to]+doors[i].t&&abs(h[to/m][to%m]-h[nx][ny])<=doors[i].p)
                    {
                        dis[nx*m+ny]=dis[to]+doors[i].t;
                        p.push((node){nx*m+ny,dis[nx*m+ny]});
                    }
                }
            }
        }
        }
        for(int i=0;i<G[to].size();i++)
        {
            int cx=G[to][i].to;
            int w=G[to][i].h;
            if(dis[cx]>imp+w)
            {
                dis[cx]=imp+w;
                p.push((node){cx,dis[cx]});
            }
        }
    }
}
int main()
{
    //freopen("c:\\output1.txt","w",stdout);
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d %d %d",&n,&m,&r);
        for(int i=1;i<=n*m+m+1;i++)
        {
            G[i].clear();
        }
        for(int i=1;i<=n;i++)
        {
            for(int j=1;j<=m;j++)
            {
                scanf("%d",&h[i][j]);
            }
        }
        for(int i=1;i<=r;i++)
        {
            scanf("%d %d %d %d %d %d",&doors[i].ax,&doors[i].ay,&doors[i].bx,&doors[i].by,&doors[i].t,&doors[i].p);
        }
        scanf("%d %d %d %d",&sx,&sy,&ox,&oy);
        for(int x=1;x<=n;x++)
        {
            for(int y=1;y<=m;y++)
            {
                if(y+1<=m)
                {
                    G[x*m+y].push_back(node{x*m+y+1,h[x][y]+h[x][y+1]});
                    G[x*m+y+1].push_back(node{(x)*m+y,h[x][y]+h[x][y+1]});
                }
                if(x+1<=n)
                {
                    G[x*m+y].push_back(node{(x+1)*m+y,h[x][y]+h[x+1][y]});
                    G[(x+1)*m+y].push_back(node{(x)*m+y,h[x][y]+h[x+1][y]});
                }
            }
        }
        dijstra();
        printf("%d\n",dis[ox*m+oy]);
    }
}
//二维写法
#include<bits/stdc++.h>
#define MAXN 101
#define INF 0x3f3f3f3f
using namespace std;
int n,m,r,sx,sy,ox,oy,t;
int h[MAXN][MAXN];
int done[MAXN][MAXN];
int dis[MAXN][MAXN];
int movx[5]={0,-1,1,0,0};
int movy[5]={0,0,0,-1,1};
struct door
{
    int ax,ay,bx,by,t,p;
}doors[11];
struct node
{
    int x,y,h;
    bool operator<(const node &A) const
    {
        return h>A.h;
    }
};
struct edge
{
    int x,y,w;
};
vector<edge>G[MAXN][MAXN];
bool legal(int x,int y)
{
    if(!(x>=1&&x<=n))
    {
        return false;
    }
    if(!(y>=1&&y<=m))
    {
        return false;
    }
    return true;
}
void makemaps()
{
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=m;j++)
        {
            for(int k=1;k<=4;k++)
            {
                int nx=i+movx[k];
                int ny=j+movy[k];
                if(!legal(nx,ny))
                {
                    continue;
                }
                G[i][j].push_back((edge){nx,ny,h[i][j]+h[nx][ny]});
            }
        }
    }
}
void dijstra()
{
    priority_queue<node>p;
    memset(done,0,sizeof(done));
    memset(dis,0x3f,sizeof(dis));
    p.push((node){sx,sy,0});
    dis[sx][sy]=0;
    while(!p.empty())
    {
        node newnode=p.top();
        p.pop();
        int tx=newnode.x;
        int ty=newnode.y;
        if(done[tx][ty])
        {
            continue;
        }
        done[tx][ty]=1;
   //     printf("%d %d\n",tx,ty);
        for(int i=1;i<=r;i++)
        {
        int ax=doors[i].ax;
        int ay=doors[i].ay;
        int by=doors[i].by;
        int bx=doors[i].bx;
        int t=doors[i].t;
        int pp=doors[i].p;
        if(!(tx>=ax&&tx<=bx&&ty>=ay&&ty<=by))
        {
            continue;
        }
        for(int nx=ax;nx<=bx;nx++)
        {
            for(int ny=ay;ny<=by;ny++)
            {
                    if(dis[nx][ny]>(dis[tx][ty]+t)&&(abs(h[tx][ty]-h[nx][ny])<=pp))
                    {
                        dis[nx][ny]=dis[tx][ty]+t;
                        p.push((node){nx,ny,dis[nx][ny]});
                    }
                }
            }
        }
        for(int i=0;i<G[tx][ty].size();i++)
        {
            int nx=G[tx][ty][i].x;
            int ny=G[tx][ty][i].y;
            int w=G[tx][ty][i].w;
            if(dis[nx][ny]>dis[tx][ty]+w)
            {
                dis[nx][ny]=dis[tx][ty]+w;
                p.push((node){nx,ny,dis[nx][ny]});
            }
        }
    }
}
int main()
{
    //freopen("c:\\output1.txt","w",stdout);
    scanf("%d",&t);
    while(t--)
    {

        scanf("%d %d %d",&n,&m,&r);
        for(int i=1;i<=n;i++)
        {
            for(int j=1;j<=m;j++)
            {
                scanf("%d",&h[i][j]);
            }
        }
        for(int i=1;i<=r;i++)
        {
            scanf("%d %d %d %d %d %d",&doors[i].ax,&doors[i].ay,&doors[i].bx,&doors[i].by,&doors[i].t,&doors[i].p);
        }
        scanf("%d %d %d %d",&sx,&sy,&ox,&oy);
        makemaps();
        dijstra();
        printf("%d\n",dis[ox][oy]);
        for(int i=1;i<=n;i++)
        {
            for(int j=1;j<=m;j++)
            {
                G[i][j].clear();
            }
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值