HDU3681

HDU3681 Prison Break

一个机器人身处N*M的矩阵中,它想要越狱逃出来,这个矩阵由N行M列的字符组成,含义如下:

(1) S表示空格子

(2) F表示机器人的起始位置

(3) G表示能量池,对于同一个能量池只能使用一次,且经过能量池的时候可以选择用或者不用,用的话能量就会变满.

(4) D表示坏格子,机器人不能走D格子

(5) Y表示开关格子,机器人必须把所有开关格子至少走一遍才能成功越狱.

机器人每走一步消耗1点能量,现在求它初始时应该带多大的容量的电池.要求该电池的容量在能使它越狱成功的条件下尽量小.

输入:包含多组实例,以0 0表示结束.每个实例第一行是N*M(1<=n,m<=15),然后是N行M列的字符矩阵.其中能量池和开关的总数不超过15.

输出:电池的最小容量.如果不能逃脱,输出-1.

分析:本题初看如果使用d[i][j][S]表示当前在(i,j)且使用能量池和走过Y点的集合为S时所剩下的最多能量. 那么要计算多少次呢?15*15*(1<<15) 还要剩余二分答案的至少10 约等于9000W次加上多组实例,可能超时.

现在这么解:其实矩阵中除了D格,其他的格子都是可以走的,且可以走任意次(但是只能使用一次能量池),关键点只有3种:起始点F点,Y点,G点.类似于TSP问题,我们要求从F点出发经过所有Y点且最多经过G点一次(这个说法不准确,因为我们在从一个Y到另外一个Y的最短路时有可能会经过Y,但是我们不使用G,我们只有指明往G走时,才使用G的能量池)所需要的最小电池.

假设电池容量为min_v,我们首先用bfs计算出这所有关键点两两间的最短距离,然后用d[i][S]表示当前在i点,已经走过了S集合的点时,所剩下的最大能量.

d[i][S+{i}] = max{ d[j][S] –dist[i][j]+chang_v(i)},从j点走到i点.如果i点是能量池,还要使得能量变满.

初值:memset(d,-1,sizeof(d));  d[f][{f}] = 0 f为起始点.

AC代码: 156ms

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
using namespace std;
int n,m;
int d[16][1<<16];
int F_x,F_y;//起始点坐标
int num_Y,Y_x[15],Y_y[15];//Y点坐标
int num_G,G_x[15],G_y[15];//G点,能量池
int dist[16][16];//关键点之间的最小距离
int g[16][16];//1表示通路,0表示不通
int num_P,P_x[16],P_y[16];
//const int up=0,down=1,left=2,right=3;
int dx[]={0,0,-1,1};
int dy[]={-1,1,0,0};
int bfs(int u,int v)
{
    if(u==v)return 0;
    queue<int> q;
    //q.clear();
    q.push(u);
    bool vis[300];//标记当前点是否已经走到
    int dist_bfs[300];//从u点到特定点的距离
    memset(vis,0,sizeof(vis));
    dist_bfs[u]=0;
    vis[u]=true;
    while(!q.empty())
    {
        int z = q.front();q.pop();
        int x = z/m, y=z%m;

        for(int d=0;d<4;d++)
        {
            int newx = x+dx[d];
            int newy = y+dy[d];
            int newz = newx*m+newy;
            if(0<=newx&&newx<n && 0<=newy && newy<m && !vis[newz] && g[newx][newy] )//newz点还没走到且该点能走
            {
                dist_bfs[newz] = dist_bfs[z]+1;
                vis[newz]=true;
                q.push(newz);
                if(newz == v)return dist_bfs[newz];
            }
        }
    }
    return -1;//从u不能到v
}

bool over(int S)//判断S是否是终结状态,只要S中的Y点都走过就行,且S走过0点
{
    for(int i=0;i<=num_Y;i++)
        if( !(S&(1<<i)) )return false;
    return true;
}
bool solve(int x)
{
    memset(d,-1,sizeof(d));
    d[0][1]=x;//在起点满格能量
    for(int S=1;S<(1<<num_P);S++)
    {
        for(int i=0;i<num_P;i++)//i在S中,且d[i][S]合法 且还有能量往前走
        {
            if( S&(1<<i) && d[i][S]!=-1 )//状态合法
            {
                for(int j=0;j<num_P;j++)if(j!=i && !(S&(1<<j)) && dist[i][j]!=-1 )//j不在S中 且从i到j有路
                {
                    if(d[i][S] < dist[i][j] ) continue;//能量不够走不过去
                    if(num_Y+1<=j &&j<=num_Y+num_G)//j为G能量池
                    {
                        d[j][S|(1<<j)] = x;//充满能量
                    }
                    else//j为Y点
                    {
                        d[j][S|(1<<j)] = max( d[j][S|(1<<j)] , d[i][S]-dist[i][j]);
                        if( over( S|(1<<j) ) )return true;//终结状态
                    }
                }
            }
        }
    }
    return false;
}
int main()
{
    while(scanf("%d%d",&n,&m)==2)
    {
        if(n==0&&m==0)break;
        num_Y=num_G=0;
        for(int i=0;i<n;i++)
            for(int j=0;j<m;j++)
            {
                char x;
                scanf(" %c",&x);
                switch(x)
                {
                    case 'S':g[i][j]=1;break;
                    case 'F':g[i][j]=1;F_x=i;F_y=j;break;
                    case 'G':g[i][j]=1;G_x[num_G]=i;G_y[num_G++]=j;break;
                    case 'Y':g[i][j]=1;Y_x[num_Y]=i;Y_y[num_Y++]=j;break;
                    case 'D':g[i][j]=0;break;
                }
            }
        num_P = 1+num_G+num_Y;
        P_x[0]=F_x;//起点
        P_y[0]=F_y;
        for(int i=1;i<=num_Y;i++)//Y点
        {
            P_x[i]=Y_x[i-1];
            P_y[i]=Y_y[i-1];
        }
        for(int i=num_Y+1;i<=num_Y+num_G;i++)//G点
        {
            P_x[i]=G_x[i-1-num_Y];
            P_y[i]=G_y[i-1-num_Y];
        }
        for(int i=0;i<num_P;i++)//计算P点集中任意两点的最短距离
        {
            for(int j=i+1;j<num_P;j++)
            {
                int u= P_x[i]*m+P_y[i];
                int v= P_x[j]*m+P_y[j];
                dist[j][i]=dist[i][j]=bfs(u,v);
            }
        }

        int min_v=300;//最多需要的能量不会超过300,可能会超过300 也不一定,但是AC
        if(!solve(min_v))printf("-1\n");
        else
        {
            int L=0,R=min_v;
            while(R>L)
            {
                int mid = L+(R-L)/2;//使得mid始终偏向L那边
                if(solve(mid))R=mid;
                else L=mid+1;
            }
            printf("%d\n",R);
        }
    }
    return 0;
}



 

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值