HDU 3681 Prison Break(状压DP + BFS)题解

题意:一张图,F是起点,Y是必须要到的点,D不能走,G可以充电。可以往四个方向走,每走一步花费一个电,走到G可以选择充满电或者不充,每个G只能充一次。问你走遍Y的最小初始点亮。number(G) + number(Y) <= 15

思路:显然Y和G都只要用一次就行,那么状压YG状态。然后BFS出任意YG两点最短路,状压DP。用&判断最终结果是不是当前状态的子集。

代码:

#include<set>
#include<map>
#include<cmath>
#include<queue>
#include<cstdio>
#include<vector>
#include<cstring>
#include <iostream>
#include<algorithm>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int maxn = 15;
const int M = maxn * 30;
const ull seed = 131;
const int INF = 0x3f3f3f3f;
const int MOD = 1e4 + 7;
int dp[1 << maxn][maxn];
char mp[maxn + 10][maxn + 10];
int n, m, sx, sy;
int cnt;
int id[maxn * maxn + 20];
int bettery[maxn * maxn + 20];
int getid(int x, int y){
    return id[x * m + y];
}
struct Node{
    int x, y, step;
};
int dis[maxn * maxn + 10][maxn * maxn + 10];
int vis[maxn + 5][maxn + 5];
int to[4][2] = {0, 1, 0, -1, 1, 0, -1, 0};
void bfs(int x, int y){
    memset(vis, 0, sizeof(vis));
    Node a, b;
    queue<Node> q;
    while(!q.empty()) q.pop();
    vis[x][y] = 1;
    a.x = x, a.y = y, a.step = 0;
    q.push(a);
    while(!q.empty()){
        a = q.front();
        q.pop();
        if(mp[a.x][a.y] != 'D' && mp[a.x][a.y] != 'S') dis[getid(x, y)][getid(a.x, a.y)] = a.step;
        for(int i = 0; i < 4; i++){
            b.x = a.x + to[i][0];
            b.y = a.y + to[i][1];
            if(b.x < 0 || b.y < 0 || b.x >= n || b.y >= m) continue;
            if(vis[b.x][b.y]) continue;
            if(mp[b.x][b.y] == 'D') continue;
            vis[b.x][b.y] = 1;
            b.step = a.step + 1;
            q.push(b);
        }
    }
}

int Fin;
bool check(int st){
    memset(dp, -1, sizeof(dp));
    for(int i = 0; i < cnt; i++){
        if(dis[cnt][i] <= st){
            if(bettery[i]) dp[1 << i][i] = st;
            else dp[1 << i][i] = st - dis[cnt][i];
        }
    }

    for(int i = 0; i < (1 << cnt); i++){
        for(int j = 0; j < cnt; j++){
            if(dp[i][j] == -1) continue;
            if(!((1 << j) & i)) continue;
            if((i & Fin) == Fin) return true;
            for(int k = 0; k < cnt; k++){
                if((1 << k) & i) continue;
                if(dis[j][k] > dp[i][j]) continue;
                dp[i | (1 << k)][k] = max(dp[i][j] - dis[j][k], dp[i | (1 << k)][k]);
                if(bettery[k]) dp[i | (1 << k)][k] = st;
            }
        }
    }

    return false;
}

int main(){
    while(~scanf("%d%d", &n, &m) && n + m){
        cnt = 0;
        Fin = 0;
        int tmp = 0;
        memset(bettery, 0, sizeof(bettery));
        for(int i = 0; i < n; i++){
            scanf("%s", mp[i]);
            for(int j = 0; j < m; j++){
                if(mp[i][j] == 'F'){
                    sx = i, sy = j;
                }
                else if(mp[i][j] == 'G'){
                    bettery[cnt] = 1;
                    id[i * m + j] = cnt++;
                }
                else if(mp[i][j] == 'Y'){
                    Fin += (1 << cnt);
                    id[i * m + j] = cnt++;
                    tmp++;
                }
            }
        }
        id[sx * m + sy] = cnt;

        if(tmp == 0){
            printf("0\n");
            continue;
        }

        memset(dis, INF, sizeof(dis));
        for(int i = 0; i < n; i++){
            for(int j = 0; j < m; j++){
                if(mp[i][j] != 'D' && mp[i][j] != 'S')
                    bfs(i, j);
            }
        }

        int l = 0, r = 1000000;
        int ans = -1;
        while(l <= r){
            int m = (l + r) >> 1;
            if(check(m)){
                r = m - 1;
                ans = m;
            }
            else l = m + 1;
        }
        printf("%d\n", ans);
    }
    return 0;
}

 

posted @ 2019-06-13 20:31  KirinSB  阅读(257)  评论(0编辑  收藏  举报