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;
}