• poj 3593 Sea Base Exploration


    状态压缩DP + 优先队列BFS

    题意:给一个矩阵图,图中只有一个*表示起点,#表示不能到达的点,从A开始有k个大写字母(一定是连续的前k个,但是每个字母的个数不一定),一种大写字母表示一种矿石,一个矿石有两个值ai,bi,表示收集这个矿石需要ai电量,另外,在一个矿石点,可以选择收集或者不收集直接走掉,收集后每走一步,需要多加bi的电量,一开始没有任何矿石,每走一步需要1电量,另外,一开始给你P电量。走的方向是上下左右四个方向。你的任务是在电量用完之前,收集到每种矿石(每种矿石只需要1个),然后回到起点,但是注意一点,在没有收集完之前,不能回到起点,因为一旦回到起点就会瞬间传送回基地。如果有的矿石收集不到,或者没足够的电量回到起点,输出Impossible,否则输出最小花费

    一看这题,没什么特别的方法,那就只好搜索了,因为要求最小花费,所以用优先队列可以达到这个效果,另外搜索过程需要剪枝。

    队列中元素的定义

    1.当前位置

    2.当前收集了哪些矿石

    3.当前花费

    对于当前收集了哪些矿石,因为矿石种类最大是10,所以可以用状态压缩,用一个长度为k的01串来表示哪些矿石被搜集了哪些没有,然后压缩为一个十进制数

    状态的转移就是枚举四个方向,然后计算新的位置和花费,以及新的收集情况

    关于收集和不收集矿石

    收集的话比较容易,就是当前点有矿石,并且还没有收集到这个矿石,就可以考虑收集(但不是一定要收集)

    不收集矿石有几种情况

    当前点是个自由地.,不存在矿石,当然不能收集;当前地有矿石,但是已经有这种矿石了,那肯定不收集;当前点有矿石,而且还没收集到这个矿石,也是可以选择不收集的,因为可能这种矿石不止一个,可以去别的地方再收集

    但是这些情况也没什么特别要注意的地方,只是说,要有这个意识,要考虑到这些情况

    最重要的是剪枝

    BFS搜索,要达到剪枝效果,最好就是入队时设置较严格的条件,防止元素重复入队。

    剪枝主要靠一个数组vis[x][y][s],表示点(x,y)的收集情况为s,这个状态是否被搜索过,如果搜索给,不用再入队,每个这样的状态只会入队一次

    为什么这样剪枝是对的。因为我们用的是优先队列,是按电量花费最小这种悠闲级出队的,那么可以知道,对于一个状态[x,y,s],如果第一次被搜索到,那么它的电量花费是最少的,以后再搜索到这个状态,电量花费不会比第一次的更少,所以没必要进来。

    另外仿照无限搜索下去的关键是,对于一个新的状态,如果要达到这个状态需要的电量超过了总电量,那么这个状态不应该入队

    再者就是,在得到一个合法的新状态后,先看看它是不是我们的目标状态,是的话直接返回答案

    最后一点,就是注意题意,不能起点不能随便走的,一旦回到起点就会传送回基地相当于一切都终止了,所以如果一个新状态的位置是起点,但是还没有搜集到全部矿石,那么这个状态是要丢弃的,因为这个状态如果入队了,说明允许了这个操作,允许在没有收集完矿石的条件下回到起点,这和题意是相矛盾的

    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #include <queue>
    #include <vector>
    using namespace std;
    #define N 25
    #define M 15
    #define NS 2510
    const int dx[4] = {-1,1,0,0};
    const int dy[4] = {0,0,-1,1};
    
    char map[N][N];
    int row,col,kind,power,sx,sy,tot;
    int A[M],B[M],per[NS];
    bool vis[N][N][NS];
    struct node  //bfs搜索时的状态
    {
        int x,y;    //当前所在的坐标
        int s;     //携带的物品的状态,压缩回来的十进制数
        int cost;  //花费了多少电量
        void func(int xx ,int yy ,int ss ,int cc)
        { x=xx; y=yy; s=ss; cost=cc; }
        bool operator<(const struct node &a) const{ 
            return cost > a.cost; }
    };
    priority_queue<struct node>que;
    
    
    void input()
    {
        cin >> row >> col >> kind >> power;
        for(int r=0; r<row; r++) cin >> map[r];
        for(int i=0; i<kind; i++) cin >> A[i] >> B[i];
        for(int r=0; r<row; r++)
            for(int c=0; c<col; c++)
                if(map[r][c] == '*')
                { sx=r; sy=c; break; }
    }
    
    void cal_per()
    {//计算在每种状态下,走一步需要的花费,先预先计算,不必一边搜索一边计算
    //    memset(per,0,sizeof(per));
        tot = (1<<kind);
        for(int s=0; s<tot; s++)
        {
            per[s] = 1;
            for(int k=0; k<kind; k++)
                if( s & (1<<k) )
                    per[s] += B[k];
        }
    }
    
    int solve()
    {
        struct node a,b;
        memset(vis,0,sizeof(vis));
        while(!que.empty()) que.pop();
        a.func(sx,sy,0,0);
        que.push(a);
        vis[sx][sy][0] = true;
        //剪枝:用vis数组标记,另外,电量>power的不能入队
        //得到一个新状态,先判断它是否是目标状态,是的话直接返回答案,不用入队
        while(!que.empty())
        {
            a = que.top(); que.pop();
            if(a.cost + per[a.s] > power) continue; //当前电量,无论怎么走都会超过power,那么直接丢弃
            for(int k=0; k<4; k++) //四个方向
            {
                int xx = a.x + dx[k];
                int yy = a.y + dy[k];
                int ss = a.s;
                int cc = a.cost + per[a.s];
                if(xx<0 || xx>=row || yy<0 || yy>=col || map[xx][yy]=='#') continue; //不是合法的位置
    
                if(xx==sx && yy==sy) 
                    if(ss == tot-1) return cc;//目标状态
                    else            continue; //回到起点又没有收集完矿石,肯定是多余的(走回头路),直接抛弃
                
                if(!vis[xx][yy][ss]) //这个状态没搜索过
                {//在这个地方不采集矿石,而不采集是有很多可能的
                    b.func(xx,yy,ss,cc);
                    que.push(b);
                    vis[xx][yy][ss] = true;
                }
    
                if(map[xx][yy]>='A' && map[xx][yy]<='Z') //有矿石
                {
                    int index = map[xx][yy] - 'A';
                    if( !(ss & (1<<index)) ) //没有这个矿石,考虑采集
                    {
                        ss |= (1<<index); //采集后的状态
                        cc += A[index];   //采集的花费
                        if(cc+A[index] > power)  continue; //超过电量,放弃
                        if(xx==sx && yy==sy && ss==tot-1) return cc; //目标状态
                        if(!vis[xx][yy][ss])
                        {
                            b.func(xx,yy,ss,cc);
                            que.push(b);
                            vis[xx][yy][ss] = true;
                        }
                    }
                }
            }
        }
        return -1;
    }
    
    int main()
    {
        int cas;
        cin >> cas;
        while(cas--)
        {
            input();
            cal_per();
            int res = solve();
            if(res == -1) cout << "Impossible" << endl;
            else          cout << res << endl;
        }
        return 0;
    }
  • 相关阅读:
    git的命令操作指南
    【flask】RestFul的基本鉴权
    linux断电修复
    centos7安装libreoffice
    java -jar 后台启动
    yum安装nginx
    rpm安装mysql
    yum安装redis
    Centos 修改yum源
    centos8安装node.js v12.18.3
  • 原文地址:https://www.cnblogs.com/scau20110726/p/2952036.html
Copyright © 2020-2023  润新知