POJ 2688 简单的TSP问题,状压DP

题意:

考虑一个被分为 W × H 网格的房间,机器人是矩形的,大小为 1 × 1。房间内的格子被分为干净的,脏的和障碍物三种。机器人不能踏上障碍物,而机器人只要经过一个脏的格子,它就会被清洁成干净的格子。
机器人每步可以向四个方向(上、下、左、右)之一走一步,进入在这个方向上和它相邻的格子。机器人可以经过一个格子多次。
你的任务是求得机器人清理干净整个房间(即把所有的脏格子清洁成干净格子)至少需要走多少步。

输入文件包含多个测试点。
每个测试点的第一行包含两个空格分隔的整数 W 和 H。之后 H 行,每行包含一个长度是 W 字符的字符串,字符串中的每个字符表示房间的一个格子,且为以下字符之一:
.:干净格子;
*:脏格子;
x:障碍物;
o:机器人所在位置;
机器人所在位置是一个干净格子。
输入文件以 0 0 结尾。

对于每个测试点,在单独的一行内输出答案。
如果无解,输出 -1。

TSP问题好难的样子,好像要用什么蚁群算法?
不过这种数据范围小的可以直接用状压DP水过。
标准做法就是f[i][S]表示当前在i,未经过的点的集合是S时还需要最少的步数。

对于这道题,因为脏点不超过10个,所以可以用状压DP,复杂度O(2^10*10)(因为起点确定,所以是10不是10^2)。

先预处理出TSP问题中的n^2条边,就是每两个脏点之间的最短路。之后直接DP即可。

#include <cstdio>
#include <queue>
#include <algorithm>
#include <cstring>
using namespace std; 

int n, m, num, d[405][405], pos[15], f[15][1<<11];
char map[405];
bool vis[405];
queue <int> q;

int id(int x, int y){
    return (x-1)*m + y;
}

void BFS(int i){
    vis[i] = 1;
    d[i][i] = 0;
    q.push(i);
    while(!q.empty()){
        int u = q.front(); q.pop();
        while(map[u] == 'x' && !q.empty()) 
        {u = q.front(); q.pop();}
        if(map[u] == 'x') break;
        if(u+1 <= id(n, m) && !vis[u+1] && u % m){
            d[i][u+1] = d[i][u] + 1;
            vis[u+1] = 1; q.push(u+1);
        }
        if(u-1 > 0 && !vis[u-1] && u % m != 1){
            d[i][u-1] = d[i][u] + 1;
            vis[u-1] = 1; q.push(u-1);
        }
        if(u+m <= id(n, m) && !vis[u+m]){
            d[i][u+m] = d[i][u] + 1;
            vis[u+m] = 1; q.push(u+m);
        }
        if(u-m > 0 && !vis[u-m]){
            d[i][u-m] = d[i][u] + 1;
            vis[u-m] = 1; q.push(u-m);
        }
    }
}

int dp(int i, int j){
    if(f[i][j] >= 0) return f[i][j];
    f[i][j] = 1 << 30;
    for(int k = 0; k < num; k++) if((j>>k)&1){
        f[i][j] = min(f[i][j], dp(k+1, j-(1<<k)) + d[pos[i]][pos[k+1]]);    
    }
    return f[i][j];
}

int main()
{
    while(scanf("%d %d", &m, &n) && n){
        memset(  d,-1, sizeof   d);
        memset(  f,-1, sizeof   f);
        memset(pos, 0, sizeof pos);
        memset(map, 0, sizeof map);
        num = 0;

        for(int i = 1; i <= n; i++){
            scanf("%s", map+id(i, 1));
        }   

        for(int i = id(n, m); i; i--){
            if(map[i] == 'o') pos[0] = i;
            if(map[i] == '*') pos[++num] = i;
        }   

        bool flag = 0;
        for(int i = 0; i <= num; i++){
            memset(vis, 0, sizeof vis); BFS(pos[i]);
            for(int j = 0; j <= num; j++){
                if(d[pos[i]][pos[j]] < 0){
                    flag = 1; break;
                }   
            }
        }   if(flag) {puts("-1"); continue;}

        int beg = 0;
        for(int i = 0; i <= num; i++){
            if(i != num) beg += (1 << i);
            f[i][0] = 0;
            for(int j = 0; j < num; j++){
                if(i == j+1) continue;
                f[i][1<<j] = d[pos[i]][pos[j+1]];       
            }
        }   
        printf("%d\n", dp(0, beg)); 
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值