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;
}
原文地址:https://www.cnblogs.com/scau20110726/p/2952036.html