📜  位掩码和动态规划 |组 2 (TSP)

📅  最后修改于: 2021-09-17 07:07:46             🧑  作者: Mango

在这篇文章中,我们将利用我们的动态规划和位掩码技术的知识来解决著名的 NP 难题之一“旅行商问题”。
在解决问题之前,我们假设读者已经了解了

  • DP与DP过渡关系的形成
  • DP 中的位掩码
  • 旅行商问题

要理解这个概念,让我们考虑以下问题:

问题描述

Given a 2D grid of characters representing 
a town where '*' represents the 
houses, '#' represents the blockage, 
'.' represents the vacant street 
area. Currently you are (0, 0) position.

Our task is to determine the minimum distance 
to be moved to visit all the houses and return
to our initial position at (0, 0). You can 
only move to adjacent cells that share exactly
1 edge with the current cell.

上面的问题就是著名的旅行商问题。
第一部分是计算两个单元格之间的最小距离。我们可以通过简单地使用 BFS 来实现,因为所有距离都是单位距离。为了优化我们的解决方案,我们将预先计算距离,以初始位置和房屋位置作为 BFS 的源点。
每个 BFS 遍历需要 O(网格大小)时间。因此,整体预计算为O(X * size_of_grid) ,其中X = 房屋数量+ 1(初始位置)
现在让我们考虑一个 DP 状态
因此,我们将需要跟踪访问过的房屋和最后访问过的房屋,以唯一标识此问题中的状态。
因此,我们将dp[index][mask]作为我们的 DP 状态。

这里,
index : 告诉我们当前房子的位置

mask :告诉我们访问过的房屋(如果在掩码中设置了第 i 个位,则这意味着第 i 个脏瓷砖已被清洁)

而 dp[index][mask] 将告诉我们访问 X(掩码中设置的位数)房屋的最小距离,对应于它们在掩码中出现的顺序,其中最后访问的房屋是位置索引处的房屋。
状态转移关系
所以我们的初始状态将是dp[0][0]这表明我们当前处于初始位置,即我们的初始位置,而掩码为 0 表示到目前为止没有访问过任何房子。
我们的最终目标状态将是dp[any index][LIMIT_MASK] ,这里 LIMIT_MASK = (1<
N = 房屋数量。
因此我们的 DP 状态转换可以表示为

dp(curr_idx)(curr_mask) = min{
    for idx : off_bits_in_curr_mask
       dp(idx)(cur_mask.set_bit(idx)) + dist[curr_idx][idx]
}

上述关系可以被形象化为,站在 curr_idx 房子和已经访问过 cur_mask 房子访问所有房子的最小距离等于 curr_idx 房子和 idx 房子之间的最小距离 + 站在 curr_idx 房子访问所有房子的最小距离idx house and by 已经
参观 ( cur_mask | (1 < ) 房子。
所以,在这里我们迭代所有可能的 idx 值,使得 cur_mask 的i 位为 0,这告诉我们i 个房子没有被访问。
每当我们有 mask = LIMIT_MASK 时,这意味着我们已经访问了镇上的所有房屋。因此,我们将上次访问的城镇(即位于 cur_idx 位置的城镇)与初始位置 (0, 0) 的距离相加。
上述实现的C++程序如下:

C++
#include 
using namespace std;
  
#define INF 99999999
#define MAXR 12
#define MAXC 12
#define MAXMASK 2048
#define MAXHOUSE 12
  
// stores distance taking source
// as every dirty tile
int dist[MAXR][MAXC][MAXHOUSE];
  
// memoization for dp states
int dp[MAXHOUSE][MAXMASK];
  
// stores coordinates for
// dirty tiles
vector < pair < int, int > > dirty;
  
// Directions
int X[] = {-1, 0, 0, 1};
int Y[] = {0, 1, -1, 0};
  
char arr[21][21];
  
// len : number of dirty tiles + 1
// limit : 2 ^ len -1
// r, c : number of rows and columns
int len, limit, r, c;
  
  
// Returns true if current position
// is safe to visit
// else returns false
// Time Complexity : O(1)
bool safe(int x, int y)
{
    if (x >= r or y>= c or x<0 or y<0)
       return false;
    if (arr[x][y] == '#')
       return false;
    return true;
}
  
  
// runs BFS traversal at tile idx
// calculates distance to every cell
// in the grid
// Time Complexity : O(r*c)
void getDist(int idx){
  
    // visited array to track visited cells
    bool vis[21][21];
    memset(vis, false, sizeof(vis));
  
    // getting current position
    int cx = dirty[idx].first;
    int cy = dirty[idx].second;
  
    // initializing queue for bfs
    queue < pair < int, int > > pq;
    pq.push({cx, cy});
  
    // initializing the dist to max
    // because some cells cannot be visited
    // by taking source cell as idx
    for (int i = 0;i<= r;i++)
        for (int j = 0;j<= c;j++)
            dist[i][j][idx] = INF;
  
    // base conditions
    vis[cx][cy] = true;
    dist[cx][cy][idx] = 0;
  
    while (! pq.empty())
    {
        auto x = pq.front();
        pq.pop();
        for (int i = 0;i<4;i++)
        {
           cx = x.first + X[i];
           cy = x.second + Y[i];
           if (safe(cx, cy))
           {
               if (vis[cx][cy])
                   continue;
               vis[cx][cy] = true;
               dist[cx][cy][idx] = dist[x.first][x.second][idx] + 1;
               pq.push({cx, cy});
            }
         }
    }
}
  
// Dynamic Programming state transition recursion
// with memoization. Time Complexity: O(n*n*2 ^ n)
int solve(int idx, int mask)
{
    // goal state
    if (mask == limit)
       return dist[0][0][idx];
  
    // if already visited state
    if (dp[idx][mask] != -1)
       return dp[idx][mask];
  
    int ret = INT_MAX;
  
    // state transiton relation
    for (int i = 0;i= INF)
        cout << "not possible" << endl;
    else
        cout << ans << endl;
  
    return 0;
}


输出:

The given grid : 
. . . . . * . 
. . . # . . . 
. * . # . * . 
. . . . . . . 
Minimum distance for the given grid : 16
The given grid : 
. . . # . . . 
. . . # . * . 
. . . # . . . 
. * . # . * . 
. . . # . . . 
Minimum distance for the given grid : not possible

注意
我们使用初始状态为dp[0][1],因为我们已将起始位置推送到房屋容器中的第一个位置。因此,我们的位掩码为1的0位设置,即我们已经为我们的访问之旅的起始位置。
时间复杂度
考虑房屋数量为n 。因此,有n * (2 n )个状态,并且在每个状态中,我们都在 n 个房屋上循环以过渡到下一个状态,并且由于记忆,我们对每个状态只进行一次循环过渡。因此,我们的时间复杂度是O(n 2 * 2 n )
推荐

  • http://www.spoj.com/problems/CLEANRBT/
  • https://www.youtube.com/watch?v=-JjA4BLQyqE

如果您希望与专家一起参加现场课程,请参阅DSA 现场工作专业课程学生竞争性编程现场课程。