HDU 3681 Prison Break - 状压dp【TSP】
题目大意:
一个机器人想越狱,监狱是一个N*M的网格。每走一格,耗费1个单位的电量。他只能带一定电量的电池, ‘F’表示起点, ‘S’表示道路可行, ‘D’表示不能经过的地点。 ‘G’表示充电器,只可充电一次,但是可以经过很多次。‘Y’表示要破坏的机关,也是只能破坏一次,但是可以经过无数次。
求机器人破坏掉所有机关,需要带的最小初始电量。
分析:
破坏所有的机关Y,且G,Y,F的总和不超过15,联想到状压dp中的TSP问题。
预处理出G,Y,F之间的两两最短距离。
原问题<=> 在这个新建的无向图上,从F出发,其余点只经过一次,经过每个点需要的最小出事电量。
我们发现如果初始电量不确定,dp不好做。
二分初始电量:用dp判定。dp[S][i]表示此状态下能剩的最多的电量,只要有能达到 【经过所有Y的状态 】的状态,就当前电量就合法。
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
#include<queue>
#define MAXN 15
#define MAXST 32768
#define INF 2000000000
int dir[4][2]={{0,1},{0,-1},{1,0},{-1,0}};
int n,m,dist[MAXN+10][MAXN+10],cntu,tar,id[MAXN+10][MAXN+10],w[MAXN+10][MAXN+10],mx,dp[MAXST+10][MAXN+10];
char mat[MAXN+10][MAXN+10];
bool vis[MAXN+10][MAXN+10],tag;
void bfs(int p,int q)
{
memset(vis,0,sizeof vis);
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
dist[i][j]=INF;
int u,v,x,y;
queue<pair<int ,int > > que;
que.push(make_pair(p,q));
vis[p][q]=true;
dist[p][q]=0;
while(!que.empty()){
u=que.front().first,v=que.front().second;
que.pop();
for(int i=0;i<4;i++){
x=u+dir[i][0],y=v+dir[i][1];
if(x<1||x>n||y<1||y>m||vis[x][y]||mat[x][y]=='D')
continue;
vis[x][y]=true;
dist[x][y]=dist[u][v]+1;
que.push(make_pair(x,y));
}
}
}
void read()
{
int p,q;
cntu=tar=0;
tag=false;
memset(id,0,sizeof id);
for(int i=1;i<=n;i++){
scanf("%s",mat[i]+1);
for(int j=1;j<=m;j++){
if(mat[i][j]=='F')
p=i,q=j;
if(mat[i][j]=='G'||mat[i][j]=='Y'){
id[i][j]=++cntu;
if(mat[i][j]=='Y')
tar|=(1<<(id[i][j]-1));
}
}
}
bfs(p,q);
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++){
if(id[i][j]){
w[0][id[i][j]]=dist[i][j];
if(dist[i][j]!=INF)
mx+=dist[i][j]*2;
if(dist[i][j]==INF&&mat[i][j]=='Y'){
tag=true;
return ;
}
}
}
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++){
if(id[i][j]){
bfs(i,j);
for(p=1;p<=n;p++)
for(q=1;q<=m;q++)
if(id[p][q])
w[id[i][j]][id[p][q]]=dist[p][q];
}
}
}
bool DP(int lmt)
{
memset(dp,-1,sizeof dp);
for(int i=1,k;i<=cntu;i++){
k=1<<(i-1);
dp[k][i]=max(dp[k][i],lmt-w[0][i]);
if(dp[k][i]>=0&&!(tar&k))
dp[k][i]=lmt;
}
int S=(1<<cntu)-1;
for(int s=0;s<=S;s++)
for(int i=1;i<=cntu;i++){
if(dp[s][i]==-1||!(s&(1<<(i-1))))
continue;
if((s&tar)==tar)
return true;
for(int j=1,k;j<=cntu;j++){
if(s&(1<<(j-1)))
continue;
k=s|(1<<(j-1));
dp[k][j]=max(dp[k][j],dp[s][i]-w[i][j]);
if(dp[k][j]>=0&&!(tar&(1<<(j-1))))
dp[k][j]=lmt;
}
}
return false;
}
void partition()
{
int l=0,r=mx,ans=-1;
while(l<=r){
int mid=(l+r)/2;
if(DP(mid))
r=mid-1,ans=mid;
else
l=mid+1;
}
printf("%d\n",ans);
}
int main()
{
while(scanf("%d%d",&n,&m)){
if(!n&&!m)
break;
read();
if(tag)
printf("-1\n");
else
partition();
}
}