HDU3681 Prison Break(壮压dp+二分+bfs)

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=3681

题意:一个机器人想越狱,他只能带一定电量的电池,'S'表示道路可行,'G'表示充电器(充满),只可充电一次,但是可以经过很多次。'F'表示起点,'Y'表示要破坏的机关,也是只需破坏一次,但是可以经过无数次。'D'表示不能经过的地点。求他能破坏所有机关,带的最小初始电量。

思路:既然我们要破坏所有的机关,又要使消耗的电量最小,那么意味着每个Y都只走一遍。我们把这些Y都标记下来走过就变为1,没走过就是0,显然需要用到壮压dp。把F、Y、G标记为特殊点,经过F、Y,状态就要变化,经过G状态不变(只是耗电量变为0),因为不一定要经过G。然后通过bfs算出每两个特殊点之间的最短距离。再用二分法寻找最小的初始化电量。

题目有点搞,大神们的代码也是看了很才看懂的,代码如下:

#include<cstdio>  
#include<cstring>  
#include<string>  
#include<iostream>  
#include<cmath>  
#include<algorithm>  
#include<queue>  
using namespace std;  
char gp[16][16];  
int nd[16][2],dp[1<<16][16],n,m,fis,ac,cnt=0,dis[16][16];  
int dr[4][2]={1,0,-1,0,0,1,0,-1}; 
 
int bfs(int x,int y,int xx,int yy)  
{  //寻找每两个特殊点之间的最短距离 
    queue<int> a,b;  
    int vd[16][16];  
    memset(vd,-1,sizeof(vd));  
    if(xx==x&&y==yy) return 0;  
    a.push(x);  
    b.push(y);  
    vd[x][y]=0;  
    while(!a.empty())  
    {  
        int nx=a.front(),ny=b.front();  
        a.pop();b.pop();  
        for(int i=0;i<4;i++)  
        {  
            int dx=nx+dr[i][0], dy=ny+dr[i][1];  
            if(dx<0||dx>=n||dy<0||dy>=m||vd[dx][dy]!=-1||gp[dx][dy]=='D')continue;  
            vd[dx][dy]=vd[nx][ny]+1;  
            a.push(dx); b.push(dy);  
            if(dx==xx&&dy==yy) return vd[dx][dy];  
        }  
    }  
    return -1;  
}  
bool check(int gas)  
{  
    memset(dp,-1,sizeof(dp));  
    dp[1<<fis][fis]=gas;  //dp[i][j] 表示到达第j号点时处于 状态 i 所能剩下的最多的油量
    int ans=-1;  
    for(int i=0; i<(1<<cnt); i++)  
        for(int j=0; j<cnt; j++)  
        { //那么从dp[i][j]出发到达其他点的时候,比如说到达k,要保证k还未被i经过,那么新的状态就是i|(1<<K)
            if((i&(1<<j))==0) continue;  //如果访问了所有情况都找不到这样的可行情况就说明这个油量是不满足的要求
            if((i&(ac))==ac)  //这个的意思是能找到一个状态经过了所有Y点后仍存在可行的油量
                if(dp[i][j]!=-1)  //说明当前的gs油量是满足答案的,然后继续二分
                    return true;  
            for(int k=0; k<cnt; k++)  
            {  
                if(dp[i][j]==-1||k==j||(i&(1<<k))||dis[j][k]==-1)continue;  
                int tem=dp[i][j]-dis[j][k]; //此时路上消耗的油量就是j到 k的距离dis[j][k]是吧 
                if(tem<0)continue;  //如果这个值小于0,说明不可能从 j走到 k
                dp[i+(1<<k)][k]=max(dp[i+(1<<k)][k],tem);  
                if(gp[nd[k][0]][nd[k][1]]=='G')dp[i+(1<<k)][k]=gas;  
            }  
        }  
        return false;  
}  
void init()  
{  
    int i,j;  
    for(int i=0; i<cnt; i++)  
        for(int j=0; j<cnt; j++)  
        {  
            if(i==j)dis[i][j]=0;  
            else  
                dis[i][j]=bfs(nd[i][0],nd[i][1],nd[j][0],nd[j][1]);  
        }  
}  
int main()  
{    
    int l,r,mid;  
    while(1)  
    {  
        scanf("%d%d",&n,&m);  
        if(n==0&&m==0)return 0;  
        getchar();  
        ac=0;cnt=0;  
        for(int i=0; i<n; i++)  
        {  
            for(int j=0; j<m; j++)  
            {  
                scanf("%c",&gp[i][j]);  
                if(gp[i][j]=='F')  
                {  
                    fis=cnt;  
                    nd[cnt][0]=i;  
                    nd[cnt][1]=j;  
                    ac+=1<<cnt;  
                    cnt++;
                }                       
                else  
                    if(gp[i][j]=='G')  
                    {  
                        nd[cnt][0]=i;  
                        nd[cnt][1]=j;  
                        cnt++;  
                    }  
                    else  
                        if(gp[i][j]=='Y')  
                        {  
                            nd[cnt][0]=i;  
                            nd[cnt][1]=j;  
                            ac+=1<<cnt;  
                            cnt++;  
                        }  
            }  
            getchar();  
        }  
        init();  
        l=0,r=300;  
        int ans=1<<30;  
        while(l<=r)  
        {  
            mid=(l+r)/2;  
            if(check(mid))  
            {  
                r=mid-1;  
                ans=min(mid,ans);  
            }  
            else  
                l=mid+1;  
        }  
        if(ans==(1<<30))  
            printf("-1\n");  
        else  
            printf("%d\n",ans);  
    }  
    return 0;  
}  


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值