动态规划 - 地铁系统中的路径计数

时间:2015-08-02 10:47:28

标签: c++ algorithm c++11 dynamic-programming

我在地铁系统中有一个站点网络。站点的数量,我可以在站点之间行进的票证的数量以及哪些站点彼此连接在文本文件中给出作为程序的输入。哪些站彼此连接保存在2D布尔矩阵中。我必须找到从0号站回到0的路径数,它使用所有票证。

以下是其中一个例子:

Example

在该示例中,有7个站和5个票。 开始并返回0,有6条路径:

0-1-2-3-4-0
0-1-5-3-4-0
0-1-6-3-4-0
0-4-3-6-1-0
0-4-3-5-1-0
0-4-3-2-1-0

我目前有一个递归解决方案,运行在O(N ^ k)(N表示站的数量,而k是票数),但我必须将其转换为迭代的动态编程解决方案O(k * N ^ 2)适用于任何输入。

#include <algorithm>
#include <fstream>
#include <iostream>
#include <map>
#include <vector>

using namespace std;


// We will represent our subway as a graph using
// an adjacency matrix to indicate which stations are
// adjacent to which other stations.
struct Subway {
  bool** connected;
  int nStations;

  Subway (int N);

private:
  // No copying allowed
  Subway (const Subway&) {}
  void operator= (const Subway&) {}
};


Subway::Subway(int N)
{
  nStations = N;
  connected = new bool*[N];
  for (int i = 0; i < N; ++i)
    {
      connected[i] = new bool[N];
      fill_n (connected[i], N, false);
    }
}

unsigned long long int callCounter = 0;
void report (int dest, int k)
{
  ++callCounter;
  // Uncomment the following statement if you want to get a feel 
  // for how many times the same subproblems get revisited
  // during the recursive solution.
  cerr << callCounter << ": (" << dest << "," << k << ")" << endl;
}


/**
 * Count the number of ways we can go from station 0 to station destination
 * traversing exactly nSteps edges.
 */
unsigned long long int tripCounter (const Subway& subway, int destination, int nSteps)
{
    report (destination, nSteps);
    if (nSteps == 1)
    {
        // Base case: We can do this in 1 step if destination is
        // directly connected to 0.
        if (subway.connected[0][destination]){
            return 1;
        }
        else{
            return 0;
        }
    }
    else
    {
        // General case: We can get to destinaiton in nSteps steps if
        // we can get to station S in (nSteps-1) steps and if S connects
        // to destination.
        unsigned long long int totalTrips = 0;
        for (int S = 0; S < subway.nStations; ++S)
        {
            if (subway.connected[S][destination])
            {
                // Recursive call
                totalTrips += tripCounter (subway, S, nSteps-1);
            }
        }
        return totalTrips;
    }
}

// Read the subway description and
// print the number of possible trips.
void solve (istream& input)
{
  int N, k;
  input >> N >> k;
  Subway subway(N);
  int station1, station2;
  while (input >> station1)
    {
      input >> station2;
      subway.connected[station1][station2] = true;
      subway.connected[station2][station1] = true;
    }
  cout << tripCounter(subway, 0, k) << endl;
  // For illustrative/debugging purposes
  cerr << "Recursive calls: " << callCounter << endl;
}




int main (int argc, char** argv)
{
  if (argc > 1) 
    {
      ifstream in (argv[1]);
      solve (in);
    }
  else
    {
      solve (cin);
    }
  return 0;
}

我没有找到解决方案。我目前没有想法,希望有人能指出我正确的方向。由于我需要为此实现自下而上的方法,我将如何开始使用最小的子问题开发动态编程表?

3 个答案:

答案 0 :(得分:3)

你应该构建一个数组T,每个步骤T[i]告诉&#34; 0和i之间有多少条路径。

对于0步,此数组为:

[1, 0, 0, ... 0]

然后,对于每个步骤,执行:

T_new[i] = sum{0<=j<n}(T[j] if there is an edge (i, j))

k步骤之后,T[0]将成为答案。

这是一个简单的Python实现来说明:

def solve(G, k):
    n = len(G)

    T = [0]*n
    T[0] = 1

    for i in xrange(k):
        T_new = [
            sum(T[j] for j in xrange(n) if G[i][j]) 
            for i in xrange(n)
        ]
        T = T_new

    return T[0]

G = [
     [0, 1, 0, 0, 1, 0, 0],
     [1, 0, 1, 0, 0, 1, 1],
     [0, 1, 0, 1, 0, 0, 0],
     [0, 0, 1, 0, 1, 1, 1],
     [1, 0, 0, 1, 0, 0, 0],
     [0, 1, 0, 1, 0, 0, 0],
     [0, 1, 0, 1, 0, 0, 0]
]

print solve(G, 5) #6

答案 1 :(得分:3)

Dynamic programming通过递归存储先前的子问题结果来工作。在您的情况下,子问题包括找到所有路径的数量,给定一些票据 k ,可以到达一个站。

在基本情况下,您有0张票,因此您可以到达的唯一站是0号站,没有路径。为了启动算法,我们假设空路径也是有效路径。

此时我会建议你先拿一张纸自己尝试一下。您需要的递归类似于

set base case (i.e. station 0 == 1 null path)    

for each ticket in [1;k]
  stations = get the stations which were reached at the previous step
  for each station in stations
    spread the number of paths they were reached with to the neighbors

return the number of paths for station 0 with k tickets

完整的DP算法,最小化将其集成到代码中所需的更改次数,如下所示

/**
* Count the number of ways we can go from station 0 to station destination
* traversing exactly nSteps edges with dynamic programming. The algorithm
* runs in O(k*N^2) where k is the number of tickets and N the number of
* stations.
*/
unsigned int tripCounter(const Subway& subway, int destination, int nSteps)
{
  map<int, vector<int>> m;
  for (int i = 0; i < nSteps + 1; ++i)
    m[i].resize(subway.nStations, 0);

  m[0][0] = 1; // Base case

  for (int t = 1; t < m.size(); ++t) { // For each ticket

    vector<int> reachedStations;
    for (int s = 0; s < subway.nStations; ++s) { // For each station
      if (m[t-1][s] > 0)
        reachedStations.push_back(s); // Store if it was reached in the previous state
    }

    for (auto s : reachedStations) {
      // Find adjacent stations
      for (int adj = 0; adj < subway.nStations; ++adj) {
        if (s == adj)
          continue;
        if (subway.connected[s][adj])
          m[t][adj] += m[t-1][s];
      }
    }
  }
  return m[nSteps][0];
}

复杂性为enter image description here。 在使用之前,请确保您已理解代码。

您将学习迭代子问题是动态编程算法中的common pattern

答案 2 :(得分:1)

我建议你考虑子问题:

  

DP [i] [a] =使用完全i票的从0到a的路径数

使用DP [0] [0] = 1初始化,DP [0] [a!= 0] = 0。

您可以通过考虑节点的所有路径来获取更新公式:

  

DP [i] [a] =所有邻居b的总和DP [i-1] [b]

有kN个子问题,每个都需要O(N)来计算,因此总复杂度为O(kN ^ 2)。

最终答案由DP [k] [0]给出。