题目: http://acm.hdu.edu.cn/showproblem.php?pid=3681
题意:一个机器人要越狱,输入一个字符矩阵,'F'代表出发点,机器人从这个点出发,电池的电量是满的;'S'是空地,机器人可以走;'G'表示充电站,经过充电站,机器人可以选择给自己的电池充满电,充完电之后,该充电站就等同于'S‘,下次经过不再充电,机器人也可以选择只经过,不充电,那么它还是‘G’ ; 'Y'表示一个开关,机器人必须关掉所有的开关,才能逃出监狱;'D'表示该位置有监测器,机器人不能走这个点,除了'D'点,其他点可以重复走。
问:机器人的电池的容量min至少是多大 才能关闭所有'Y'之后 逃出监狱?如果可以逃出来,输出min,逃不出来,输出-1;
围观大神的解题报告: hdu3681解题报告
题解:首先,我们要明确,题目就是要求我们必须经过F和所有的Y至少一次,这有点类似tsp,于是想到可以状态压缩,但是G怎么处理,这很头疼,因为经过'G',我们可以充电 也可以不充电。所以我们决定把'G'点也进行压缩,'F' ‘G’ ‘Y’ 我称为关键点,我们要对这三类点进行状态压缩,具体分为三步:
第一步,把所有的F,Y,G 视为关键点,记录下来, 用bfs算出这些关键点两两之间的最短距离dis[i][j],我们把'G' ‘Y’ ‘F’ 全部视为普通的'S'。也就是说,经过'G',我们不充电 ;经过'Y'点,我们也不管关闭开关的事情,那什么时候充电和关闭开关呢 ?等到我们自己在状态转移时,再进行 充电 / 关闭开关,这样就保证了'G'点我们只充一次电 , 但是这样对于一个'Y',我们就可能重复走了两次,因为dis[i][j]可能经过了其他的'Y',但没有标记,我们还要去再关闭那个‘Y',这显然不是最优,不过没关系,状态转移的时候,我们都会进行比较,最后取到最优的情况。
第二步,二分,答案明显 在 0 ~ 矩阵的行*矩阵的列 之间,二分出一个mid,然后对mid检查,看是否符合条件
第三步,写一个检查函数 bool ok(int mid); 检查mid是否合法;函数体:
用 类似 tsp的转移方程,我们把'Y' 'G' 'F' 压缩成一个状态,如果某个开关‘Y’还没有明确关闭,则Y相应位置0;如果某个充电站还有点,该充电站G相应位置为0;因为题目指出'Y'和'G'的总数不会超过15,所以状态s在1到1<<16之内
状态 : dp[s][i] 表示 到达状态s,位于关键点i,剩余的最大电量;
边界 : dp[1<<start][start] = mid;
当((s & en) == en) && dp[en][i] >=0 即可返回 true, 其中 en表示经过F和Y一次的状态,可以在统计关键点的时候,得到en。
#include<stdio.h>
#include<memory.h>
#include<queue>
using namespace std;
int r,c;//字符矩阵的行和列
const int maxn = 16;
char map[maxn][maxn];//存放输入的字符矩阵
int sign[maxn][maxn];//标记数组,把关键点标记起来
struct Point
{
int x,y;
int val;
Point(){}
Point(int xx,int yy){x = xx ; y =yy;val=0;}
}p[maxn+1];
int cnt = 0;//关键点的个数 编号 1~cnt
int en = 0;//终止状态,即 经过F和Y一次的状态
int start = 0;//出发点
int dis[maxn][maxn];//关键点两两之间的最短距离
int tmp[maxn][maxn];//临时数组,计算最短距离用到
int dir[4][2]
{
{-1,0},{1,0},{0,-1},{0,1}
};
bool check(int x,int y)
{
if(x<0||y<0) return false;
if(x>=r||y>=c) return false;
if(map[x][y]=='D') return false;
return true;
}
void bfs(int i) //统计关键i到其他关键点 之间的最短距离
{
memset(tmp,-1,sizeof(tmp)); // tmp[i][j] 表示点(i,j) 距离 关键点 p[i] 的最短距离
tmp[p[i].x][p[i].y] = 0;
//开始bfs
queue<Point>q; //存放有更新的点
q.push(p[i]);
while(q.size())
{
Point top = q.front();
q.pop();
for(int ii=0;ii<4;ii++)
{
int newX = top.x + dir[ii][0];
int newY = top.y + dir[ii][1];
bool flag = false; //该点是否被更新
if(check(newX,newY))//合法
{
if(tmp[newX][newY]==-1) //还没求出来,直接赋值
{
tmp[newX][newY] = tmp[top.x][top.y] + 1;
flag = true;
}else if(tmp[newX][newY] > tmp[top.x][top.y] + 1){//更新
tmp[newX][newY] = tmp[top.x][top.y] + 1;
flag = true;
}
}
if(flag)//有更新,将该点压入队列
{
Point pp(newX,newY);
q.push(pp);
}
}
}
//统计i到其他关键点的最短距离
for(int ii=0;ii<r;ii++)
{
for(int jj=0;jj<c;jj++)
{
if(sign[ii][jj]>=0)//是关键点
{
dis[i][sign[ii][jj]] = tmp[ii][jj];
}
}
}
}
int dp[1<<maxn][maxn];
int max(int a,int b)
{
return a>b?a:b;
}
bool ok(int all)
{
memset(dp,-1,sizeof(dp));
dp[1<<start][start] = all;//边界,出发点是满电量
for(int s=1;s<(1<<cnt);s++) //到达s状态
{
for(int i=0;i<cnt;i++)//当前位于i
{
if(dp[s][i]<0 || !(s&(1<<i))) continue; //这个状态合法,且s包含i
if((s&en)==en) return true; //如果已经满足了条件,直接返回true
for(int j=0;j<cnt;j++)//转移到j
{
if(i==j || dis[i][j]==-1) continue;//如果i和j不相通,或者j==i
if(dp[s][i] < dis[i][j]) continue;//如果剩余的能量不够我们转移到j
if(s&(1<<j)) continue;//如果s已经包含了j
int tmp = (s|(1<<j));//推出下一个状态
int x = p[j].x;
int y = p[j].y;
if(map[x][y]=='G')//明确地指出 我要充电
{
dp[tmp][j] = all;
}else { //或者 明确地关闭某个 开关'Y'
dp[tmp][j] = max(dp[tmp][j],dp[s][i]-dis[i][j]);
}
}
}
}
return false;
}
int main()
{
while(true)
{
scanf("%d%d",&r,&c);
if(r==0 && c==0)break;
memset(sign,-1,sizeof(sign));
memset(dis,-1,sizeof(dis));
cnt = en = 0;
//1.记录所有的非'S'点,'G' 'Y' 'F'他们是关键点,我们要让机器人至少经过Y和F点 一次
for(int i=0;i<r;i++)
{
scanf("%s",map[i]);
for(int j=0;j<c;j++)
{
if(map[i][j]=='G')
{
p[cnt].x = i;
p[cnt].y = j;
sign[i][j] = cnt++;//标记
}else if(map[i][j]=='F')
{
en = en + (1<<cnt);
p[cnt].x = i;
p[cnt].y = j;
start = cnt;
sign[i][j] = cnt++;//标记
}else if(map[i][j]=='Y')
{
en = en + (1<<cnt);
p[cnt].x = i;
p[cnt].y = j;
sign[i][j] = cnt++;//标记
}
}
}
//2.计算关键点 之间的最短距离
for(int i=0;i<cnt;i++)
{
dis[i][i] = 0;
bfs(i);
}
//3.二分枚举每一个解 然后用 状态压缩dp 判断该解是否可行
int ll=0,rr=r*c;
int rs = -1;
while(ll<=rr)
{
int mid = (ll+rr)/2;
if(ok(mid))
{
rs = mid;
rr = mid - 1;
// printf("ok....mid == %d\n",mid);
}else{
ll = mid + 1;
// printf("no..\n");
}
}
printf("%d\n",rs);
}
return 0;
}//187ms
...蒟蒻渣科看到这题掉下了眼泪