HDU 3681 Prison Break (状压DP+二分)

题意:给你一个n*m的图,机器人只能上下左右移动,移动一格消耗一点能量,F是起点,D是不可经过的点,S是空地,G是充能的站点,Y是必须经过的点,整个过程机器人必须让自己的能量尽可能的少,并且经过所有的Y,问机器人的最小能量容量是多少。

思路:因为‘G’和‘Y’的数量和T小于等于15,所以用这个来表示状态。

设'Y'的点数为Y。那么F标号为0,'Y'的点依次标号为1,2,....Y 。‘G’标号为Y+1,Y+2,....T。

状态S:第i位为1表示经过了第i-1个点。以此来二分答案。

设能量容量为k,dp[S][v]为经过状态S中的所有点,最后到达v的最大剩余能量。

状态转移方程:dp[S][v] = max(dp[S][v],dp[S-{v}][u] - G[u][v]) 如果dp[S][v] >= 0 且v > Y 的话,dp[S][v] = k。


我的代码:

#include<cstdio>
#include<iostream>
#include<map>
#include<queue>
#include<cstring>
#include<algorithm>

using namespace std;
typedef pair<int,int> P;
const int maxn = 15;
const int inf = 0x3f3f3f3f;
const int dx[4] = {1,-1,0,0};
const int dy[4] = {0,0,1,-1};

char maps[maxn][maxn];
int n,m,T,G[maxn][maxn];
int dp[1<<maxn][maxn];
map<P,int> M;
int Y;

struct Nod{
    int x,y,step;
};
bool vis[maxn][maxn];
queue<Nod> que;

void init(){
    M.clear();
    int tmp = 1 << maxn;
    //memset(G,-1,sizeof(G));
    for(int i=0;i<maxn;i++) fill(G[i],G[i]+maxn,inf);
    //for(int i=0;i<tmp;i++) fill(dp[i],dp[i]+maxn,inf);
}

void bfs(int x,int y,int s){
    //cout<<x<<" "<<y<<" "<<s<<endl;
    Nod cur,next;
    memset(vis,0,sizeof(vis));
    cur.x = x;cur.y = y;cur.step = 0;
    que.push(cur);
    vis[x][y] = true;

    while(!que.empty()){
        cur = que.front();que.pop();
        //cout<<cur.x<<" "<<cur.y<<" "<<cur.step<<endl;
        if(cur.step > 0 && (maps[cur.x][cur.y] == 'G' || maps[cur.x][cur.y] == 'Y')){
            int pos = M[P(cur.x,cur.y)];
            G[s][pos] = cur.step;
        }
        for(int i=0;i<4;i++){
            next.x = cur.x + dx[i];
            next.y = cur.y + dy[i];
            next.step = cur.step + 1;
            if(next.x < 0 || next.x >= n || next.y < 0 || next.y >= m || vis[next.x][next.y] || maps[next.x][next.y] == 'D') continue;
            vis[next.x][next.y] = true;
            que.push(next);
        }
    }
}

void build_graph(){
    T = 1;
    for(int i=0;i<n;i++){
        for(int j=0;j<m;j++){
            if(maps[i][j] == 'Y') M.insert(make_pair(P(i,j),T++));
            if(maps[i][j] == 'F') M.insert(make_pair(P(i,j),0));
        }
    }
    Y = T - 1;
    for(int i=0;i<n;i++){
        for(int j=0;j<m;j++){
            if(maps[i][j] == 'G') M.insert(make_pair(P(i,j),T++));
        }
    }
    for(int i=0;i<n;i++){
        for(int j=0;j<m;j++){
            if(maps[i][j] == 'F' || maps[i][j] == 'Y' || maps[i][j] == 'G'){
                bfs(i,j,M[P(i,j)]);
            }
        }
    }
}

bool check(int S){
    for(int i=0;i<Y;i++){
        if(!(S >> i & 1)) return false;
    }
    return true;
}

bool binSearch(int k){
    int Ed = 1 << T;
    bool flag;
    memset(dp,-1,sizeof(dp));
    dp[0][0] = k;
    for(int S = 1; S < Ed ; S++){
        flag = false;
        for(int v = 1; v <= T ; v++){
            if(!(S >> (v - 1) & 1)) continue;
            int S0 = S - (1 << (v - 1));
            if(S0 == 0){
                dp[S][v] = max(dp[S][v],dp[0][0] - G[0][v]);
            }
            else{
                for(int u = 1;u <= T;u++){
                if(!(S0 >> (u - 1) & 1) || dp[S0][u] == -1) continue;
                dp[S][v] = max(dp[S][v],dp[S0][u] - G[u][v]);
                }
            }
            if(dp[S][v] >= 0){
                flag = true;
                if(v > Y) dp[S][v] = k;
            }
        }
        if(check(S) && flag){
            return true;
        }
    }
    return false;
}

void solve(){
    T -= 1;
    int lb = 0,rb = 1000;
    while(rb - lb > 1){
        int mid = (lb + rb) >> 1;
        if(binSearch(mid)) rb = mid;
        else lb = mid;
    }
    if(rb == 1000) printf("-1\n");
    else printf("%d\n",rb);
}

int main(){
    while(~scanf("%d%d",&n,&m)){
        if(n + m == 0) break;
        init();
        for(int i=0;i<n;i++) scanf("%s",maps[i]);
        build_graph();
        solve();
    }
    return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值