📜  位屏蔽和动态编程套装2(TSP)

📅  最后修改于: 2021-04-23 17:22:00             🧑  作者: 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 :告诉我们所参观的房屋(如果在mask中设置了ith位,则表示已清理了ith脏砖)

而dp [index] [mask]会告诉我们访问X(掩码中设置的位数)房屋的最小距离,该距离与它们在掩码中的出现顺序相对应,其中最后访问的房屋是位置索引处的房屋。
国家过渡关系
因此,我们的初始状态将为dp [0] [0],这表明我们当前位于初始图块,这是我们的初始位置,而mask为0则表明到目前为止尚未访问过任何房屋。
我们的最终目标状态将为dp [any index] [LIMIT_MASK] ,此处LIMIT_MASK = (1 << N)– 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房屋之间的距离的最小值+站立在____时访问所有房屋的最小距离idx房子和已经
参观( cur_mask |(1 << idx) )房屋。
因此,在这里,我们遍历所有可能的idx值,以使cur_mask的i位为0,这告诉我们i个房子没有被访问。
每当我们戴上面具= 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