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;

}

 

你可能感兴趣的:(exp)