答:
本问题虽然沿用前一个问题的数学模型DAG,但是应充分利用只有很少的格子上有豆子的条件,在计算中尽量排除那些没有豆子的格子,才能降低时间复杂度。
将棋盘上有豆子的格点两两连起来,忽略没有豆子的格点,组成一个有向无环图(DAG)。连接任意两点之间的边的指向同样要保证是从左向右,从上到下。无法满足此要求的两点不连接。由于每个格子只有一个豆子,所以这个DAG的每条边的权重都是相等的,不妨设为1。要让吃豆人吃到最多的豆子,就变成了搜索此DAG的最长路径的问题。
解答分为3部分:一、修改对DAG进行拓扑排序的函数,在保证排序正确性的基础上降低其复杂度;二、求解;三、时间复杂度分析。
1修改对DAG进行拓扑排序的函数
在拓扑排序中,从顶点u到顶点v的每个有向边uv,u在排序中都在v之前。这里我们重写上题解答中的拓扑排序函数topologicalSortUtil()。新函数不再对棋盘上每一个格子进行排序,而是只对含有豆子的格子进行拓扑排序。
排序步骤如下:
1) 找到一个含有豆子的格子u,其坐标为(x,y),同时建立一个变量,记录以u为起点的边数。
2) 找到另外一个有豆子的格子v,其坐标为(s,t)。

3) 假如s>=x 且 t>=y,则u可以到达v,于是以u为起点的边数加1,并在两者之间建立一条边,从u指向v;否则则不加1。
4) 重复2)和3)的操作,遍历所有的k-1个格子,算出以u为起点的边总数。
5) 重复1)2)3)4)的操作,遍历所有的k个格子,算出每个有豆子的格子的边数。
6) 准备一个stack,存放拓扑排序后的顶点集合。
7) 找到所有的以其自身为起点的边总数为0的格子,将它们放入stack。既然不存在以它们为起点的边,那么这些格子之间不会有边连接。根据拓扑排序的性质,“从顶点u到顶点v的每个有向边uv,u在排序中都在v之前”,这些格子不论在stack中彼此次序如何,都不会违背这个性质。
8) 找到所有的以其自身为起点的边总数为1的格子,将它们放入stack。这些格子彼此之间不会相互连接。(假如这些格子彼此之间存在边连接,那么作为这条边起点的格子的边总数将至少等于2—这条边的终点的格子算一个,而既然这条边的终点的格子的边总数为1,以其为起点的边指向的格子又算一个—也就破坏了“边总数为1的”的假设。)由于相互不连接,它们相互的次序不会破坏拓扑排序的性质。
9) 找到所有的以其自身为起点的边总数为p(p=2,3,4,….)的格子,将它们放入stack。这些格子彼此之间不会相互连接。由于相互不连接,它们相互的次序不会破坏拓扑排序的性质。
10) 重复第9步,直到所有的含有豆子的格子全被录入stack。
11) 将格子(0,0)作为最顶层的元素放入stack。拓扑排序完成。
2 求解
1) 输入变量是一个vector,vector由所有含有豆子的格子组成。每个格子用一个二元结构体表示。结构体的两个分量分别表示格子的横坐标和纵坐标。
2) 按照前面的步骤对这个vector进行拓扑排序。排序的同时也生成了相应的DAG。
3) 调用前一个问题中的函数Graph::longestPath(int s)返回最长路径。
以下是代码清单:
AdjListNode(int _v, int _w)
int getWeight() { return weight; }
unique_ptr<int[]> m_upCount;
void addEdge(int u, int v, int weight);
void longestPath(int s, stack<int>);
stack<int> topologicalSortUtil(vector);
pPath = new list<int>[V];
m_upCount.reset(new int[V]);
void Graph::addEdge(int u, int v, int weight)
AdjListNode node(v, weight);
stack<int> Graph::topologicalSortUtil(vector vecPos)
int iLen = vecPos.size();
for (int k = 0; k < iLen; k++)
for (int l = 0; l < iLen; l++)
if (v.x >= u.x && v.y >= u.y)
while (Stack.size() < iLen)
for (int k = 0; k < iLen; k++)
if (m_upCount[k] == iLevel)
void Graph::longestPath(int s, stack<int> Stack)
for (int i = 0; i < V; i++)
while (Stack.empty() == false) {
for (i = adj[u].begin(); i != adj[u].end(); ++i)
if (dist[i->getV()] < dist[u] + i->getWeight())
dist[i->getV()] = dist[u] + i->getWeight();
pPath[i->getV()].assign(pPath[u].begin(), pPath[u].end());
pPath[i->getV()].push_back(i->getV());
for (int i = 0; i < V; i++)
cout << "length: -----------";
(dist[i] == NINF) ? cout << "unReachable" : cout << dist[i];
for (const auto & item : pPath[i])
vector vecNodes = { { 0, 0 }, { 0, 3 }, { 1, 2 }, { 1, 4 }, { 1, 5 }, { 1, 6 },
{ 2, 0 }, { 2, 1 }, { 2, 4 }, { 3, 3 }, { 3, 4 }, { 4, 4 }, { 5, 0 }, { 5, 2 }, { 5, 3 },
{ 6, 1 }, { 6, 3 }, { 6, 5 } };
Graph g(vecNodes.size());
stack<int> Stack = g.topologicalSortUtil(vecNodes);
cout << "Following are longest distances from "

运行结果:

最大路径长度是6,途径格子0-6-7-9-14-16-17
棋盘上豆子分布如图,数字是每个格子的编号:

3 时间复杂度分析
整个程序分为两部分:拓扑排序函数topologicalSortUtil和计算最长路径函数longestPath
- 在拓扑排序的过程中遍历了所有的含有豆子的格子(k个),并且对每个格子,又遍历了其他含有豆子的格子(k-1个)。因此拓扑排序的过程的时间复杂度为O(k2)。
- longestPath函数遍历了DAG的所有顶点,也就是所有的含有豆子的格子,共k个。对于每个顶点,又更新了与之相连的所有其他顶点到(0,0)的距离,这些顶点的个数不超过k-1。所以longestPath函数带来的时间复杂度不超过O(k^2)。
综合1与2,整个程序的时间复杂度为O(k2" role="presentation">k2)