NYOj-WAJUEJI which home strong!-BFS + 优先队列

来源:互联网 发布:java sql触发器写法 编辑:程序博客网 时间:2024/04/27 07:56

问题描述:
第一个数T,T组测试数据。
两个数 n, m; ( 0< n , m <= 100 ) 表示一个h行m列的二维地图。
接下来n行每行m 个字符。
‘s’ 表示弟弟目前所在位置。
‘# ’表示此处为一座山。为了节省体力,不从此处通行。
从‘A’-‘Z’表示各地的经济水平,对应1-26,路过对应字符的地区需要交对应的生活费。
‘l’表示蓝翔技校的所在地。
s 与 l 均为小写字母。
弟弟只能走四个方向。
代码①:DFS

char maze[110][110];//maze用来储存地图int book[110][110],mcost,n,m;void dfs(int x,int y,int cost)//走到点(x,y)时,路费为cost{    if(maze[x][y] == 'l')//边界    {        cost -= 'l'- 'A' + 1;//将多加的路费减去        mcost = min(mcost,cost);//更新答案        return;    }    if(cost > mcost)//最优化剪枝,不过没什么用,依然T        return;    for(int i = 0; i <= 3; ++i)//上下左右    {        int tx = x + dirx[i];        int ty = y + diry[i];        if(tx < 1 || tx > n || ty < 1 || ty > m)//越界            continue;        if(maze[tx][ty] != '#' && book[tx][ty] == 0)//不是山且没被标记过        {            book[tx][ty] = 1;//标记            dfs(tx,ty,cost + (int)maze[tx][ty] - 'A' + 1);            book[tx][ty] = 0;//取消标记        }    }}int main(){    int _, sx,sy;    scanf("%d\n",&_);//输入数据组数    while(_--)    {        memset(book,0,sizeof(book));//重置标记数组        mcost = inf;//重置        //cin >> n >> m;// 玄学问题,用cin会出错        scanf("%d%d\n",&n,&m);//一定要加\n,否则会出错!!!        for(int i = 1; i <= n; ++i)//输入地图            for(int j = 1; j <= m; ++j)            {                scanf("%1c\n",&maze[i][j]);                if(maze[i][j] == 's')                {                    sx = i;//记录起点                    sy = j;                }            }        book[sx][sy] = 1;//标记起点        dfs(sx,sy,0);//深度优先搜索        if(mcost == inf)//打印            cout << "-1" << endl;        else            cout << mcost << endl;    }    return 0;}

代码②:BFS(AC)

struct node//队列元素{    int x;    int y;    int cost;};queue<node>pq;//队列int maze[110][110];int mcost,n,m,book[110][110];node t,next_t;int main(){    int _, sx,sy;    scanf("%d\n",&_);    while(_--)    {//        for(int i = 1; i <= n; ++i)//错误,n,m还未被输入//            for(int j = 1; j <= m; ++j)//                book[i][j] = inf;        scanf("%d%d\n",&n,&m);//加上\n        //初始化        mcost = inf;        for(int i = 1; i <= n; ++i)            for(int j = 1; j <= m; ++j)                book[i][j] = inf;        for(int i = 1; i <= n; ++i)            for(int j = 1; j <= m; ++j)            {                scanf("%1c\n",&maze[i][j]);//加上\n                if(maze[i][j] == 's')                {                    sx = i;//记录起点                    sy = j;                }            }        t.x = sx;        t.y = sy;        t.cost = 0;        pq.push(t);//将起点入队        while(!pq.empty())//当队列非空        {            t = pq.front();//出队            book[t.x][t.y] = t.cost;//将到达点(x,y)所花费的路费记为cost            for(int i = 0; i <= 3; ++i)//上下左右            {                next_t.x = t.x + dirx[i];                next_t.y = t.y + diry[i];                if(next_t.x < 1 || next_t.x > n || next_t.y < 1 || next_t.y > m)//越界                    continue;                if(maze[next_t.x][next_t.y] == '#')//山                    continue;                if(maze[next_t.x][next_t.y] == 'l')//终点                {                    mcost = min(mcost,t.cost);//更新答案                    continue;//继续                }                next_t.cost = t.cost + maze[next_t.x][next_t.y] - 'A' + 1;//计算到下一个点的路费                if(book[next_t.x][next_t.y] > next_t.cost)//如果到下一个点的路费大于其标记,则没有必要对其进行扩展                {                    book[next_t.x][next_t.y] = next_t.cost;//虽然入队后会修改标记,但是在这儿就修改会加速算法,险过                    pq.push(next_t);//入队                }                pq.pop();//出队            }        }        if(mcost == inf)//打印            cout << "-1" << endl;        else            cout << mcost << endl;    }    return 0;}

思考:这道题的关键点在标记上,如果不标记,程序会陷入死循环;而如果只标记为1,程序效率会很低下。不错的办法是将其标记为花费,如果以某种方式扩展而来的点的花费超过了当前的标记,则其没有被加入队列的必要
代码③:BFS + 优先队列(AC)

struct node//队列元素{    int x;    int y;    int cost;};struct cmp//自定义{    bool operator()(const node &a,const node &b)    {        return a.cost > b.cost;//较小的花费值排在前面    }};priority_queue<node,vector<node>,cmp> pq;char maze[110][110];int mcost,n,m,book[110][110],flag;node t,next_t;int main(){    int _, sx,sy;    scanf("%d\n",&_);    while(_--)    {        scanf("%d%d\n",&n,&m);        //重置        flag = 0;        mcost = inf;        for(int i = 1; i <= n; ++i)            for(int j = 1; j <= m; ++j)                book[i][j] = inf;        //读入地图        for(int i = 1; i <= n; ++i)            for(int j = 1; j <= m; ++j)            {                scanf("%1c\n",&maze[i][j]);                if(maze[i][j] == 's')                {                    sx = i;//记录起点                    sy = j;                }            }        t.x = sx;        t.y = sy;        t.cost = 0;        pq.push(t);//将起点入队        while(!pq.empty())        {            t = pq.top();            book[t.x][t.y] = t.cost;//记录            pq.pop();//需要尽快出队            for(int i = 0; i <= 3; ++i)            {                next_t.x = t.x + dirx[i];                next_t.y = t.y + diry[i];                if(next_t.x < 1 || next_t.x > n || next_t.y < 1 || next_t.y > m)//越界                    continue;                if(maze[next_t.x][next_t.y] == '#')//山                    continue;                if(maze[next_t.x][next_t.y] == 'l')//终点                {                    mcost = min(mcost,t.cost);                    //flag = 1;//错误                    //break;//错误,不能直接break                    continue;                }                next_t.cost = t.cost + maze[next_t.x][next_t.y] - 'A' + 1;                if(book[next_t.x][next_t.y] > next_t.cost)                {                    book[next_t.x][next_t.y] = next_t.cost;//加速                    pq.push(next_t);//入队                }            }            //if(flag == 1)//错误                //break;        }        if(mcost == inf)//打印            cout << "-1" << endl;        else            cout << mcost << endl;    }    return 0;}

解决方法:对比代码②和③,发现关键点还是标记的使用。用了优先队列,速度有了大幅的提升,但是还不够快。对照一下别人的代码,他找出第一组解后直接返回,而我还需要继续不断比较,如果直接返回的话,会WA。大概我写的是假的BFS + 优先队列吧……
代码④:标程

char maze[105][105];//地图int n,m,sx,sy,book[105][105];struct node{    int x;    int y;    int cost;    friend bool operator < (const node &a,const node &b)//友元函数重载操作符    {        return a.cost > b.cost;    }};node t,next_t;int BFS(){    priority_queue<node> pq;//定义成局部变量!!!要么就每一次都将其清空,否则会影响接下来的数据    t.x = sx;    t.y = sy;    t.cost = 0;    pq.push(t);//入队    book[t.x][t.y] = 1;//标记    while(!pq.empty())    {        t = pq.top();//访问栈顶元素        pq.pop();//尽快出队        if(maze[t.x][t.y] == 'l')//如果当前点为终点            return t.cost;//直接返回        for(int i = 0; i <= 3; i++)//上下左右        {            next_t.x = t.x + dirx[i];            next_t.y = t.y + diry[i];            next_t.cost = t.cost;            if(next_t.x >= 1 && next_t.y >= 1 && next_t.x <= n && next_t.y <= m && book[next_t.x][next_t.y] == 0 && maze[next_t.x][next_t.y] != '#')//如果不越界、没被访问过、不是山            {                if(maze[next_t.x][next_t.y] != 'l')//如果下一个点是终点,就不加其路费                    next_t.cost = next_t.cost + maze[next_t.x][next_t.y] - 'A' + 1;                pq.push(next_t);//入队                book[next_t.x][next_t.y] = 1;//标记            }        }    }    return -1;//无解}int main(){    int _;    scanf("%d\n",&_);    while(_--)    {        memset(book,0,sizeof(book));//重置        scanf("%d%d\n",&n,&m);        for(int i = 1; i <= n; i++)            for(int j = 1; j <= m; j++)            {                scanf("%1c\n",&maze[i][j]);                if(maze[i][j] == 's')                {                    sx = i;//记录起点                    sy = j;                }            }        int ans = BFS();        printf("%d\n",ans);//打印    }}

解决方法:标程只跑了112ms。它快就快在一到达终点,所花路费就是最小值。这是为什么呢?我们最初对起点进行扩展,扩展出来的点的路费由小到大在优先队列中排列,接下来再对堆顶的点进行扩展,就像几个小孩子嬉戏打闹你追我赶。最先到达终点的点所耗路费一定是最小的,否则先到达终点的就不是它了。这样一来,不必对所有路径、所有路费的值进行比较,节省了大量时间。

原创粉丝点击