UVa OJ 125 - Numbering Paths (路径计数)

Background
背景

Problems that process input and generate a simple "yes" or "no" answer are called decision problems. One class of decision problems, the NP-complete problems, are not amenable to general efficient solutions. Other problems may be simple as decision problems, but enumerating all possible "yes" answers may be very difficult (or at least time-consuming).
仅由简单的“是”或“否”来回答的问题被称作决策问题,其中属于NP完全的一类决策问题是难以快速求解的。另一些非决策的NP完全问题的答案可能与决策问题一样简单,但枚举所有“是”的答案则会非常困难(至少要花大量的时间)。

This problem involves determining the number of routes available to an emergency vehicle operating in a city of one-way streets.
本问题是要来计算:在一个所有街道都为单行线的城市中,一辆急救车可以通行的路径的数量。

 

The Problem
问题

Given the intersections connected by one-way streets in a city, you are to write a program that determines the number of different routes between each intersection. A route is a sequence of one-way streets connecting two intersections.
给定城市中一组由单行线相连的路口,你要写一个程序来计算出各路口间有多少条不同的路径。一条路径由一系列将两个路口连通的单行线组成。

Intersections are identified by non-negative integers. A one-way street is specified by a pair of intersections. For example, j k indicates that there is a one-way street from intersection j to intersection k. Note that two-way streets can be modeled by specifying two one-way streets: j k and k j.
各路口均由非负整数标记,每条单行线由两个路口表示,如j k表示存在一条单行线从路口j到路口k。注意双行线可以由两条单行线模拟,比如:j k和k j。

Consider a city of four intersections connected by the following one-way streets:
观查下面一个拥有四个路口并由单行线相互连通的城市:

0  1
0  2
1  2
2  3

There is one route from intersection 0 to 1, two routes from 0 to 2 (the routes are 0->1->2 and 0->2 ), two routes from 0 to 3, one route from 1 to 2, one route from 1 to 3, one route from 2 to 3, and no other routes.
在0路口到1路口之间只有1条路径,0到2之间有两条路径(0->1->2和0->2),由0到3有两条。1到3有一条,2到3有1条,这些就是全部的路径了。

It is possible for an infinite number of different routes to exist. For example if the intersections above are augmented by the street 3 2 , there is still only one route from 0 to 1, but there are infinitely many different routes from 0 to 2. This is because the street from 2 to 3 and back to 2 can be repeated yielding a different sequence of streets and hence a different route. Thus the route 0->2->3->2->3->2 is a different route than 0->2->3->2.
有可能不同的路径有无数条。比如对于上面讨论过路口2和3,增加一条单行线3 2,此时由0到1仍只有1条路径,但从0到2就有无数条不同的路径。这是由于从2到3后又可以折返回到2,每次往复折返都会产生一条新的不同路径。比如0->2->3->2->3->2与0->2->3->2就是两条不同的路径。

 

The Input
输入

The input is a sequence of city specifications. Each specification begins with the number of one-way streets in the city followed by that many one-way streets given as pairs of intersections. Each pair j k represents a one-way street from intersection j to intersection k. In all cities, intersections are numbered sequentially from 0 to the "largest" intersection. All integers in the input are separated by whitespace. The input is terminated by end-of-file.
输入为一系列城市的定义。每个定义最前面的数据是城市中的单行线数量,后面跟着该数量的单行线定义,每个单行线由一对由整数标记的路口给出。每一对整数j k表示一条单行线由路口j到k。在所有的城市中,路口的编号均由0到“最大”的一个路口,各不相同。输入的所有整数都由空格隔开。输入由EOF表示结束。

There will never be a one-way street from an intersection to itself. No city will have more than 30 intersections.
不存在起点和终点路口相同的单行线。每个城市最多30个路口。

 

The Output
输出

For each city specification, a square matrix of the number of different routes from intersection j to intersection k is printed. If the matrix is denoted M, then M[j][k] is the number of different routes from intersection j to intersection k. The matrix M should be printed in row-major order, one row per line. Each matrix should be preceded by the string "matrix for city k" (with k appropriately instantiated, beginning with 0).
对于每一个城市的定义,要按矩阵的格式打印出每一对路口j和i之间存在的不同路径的数量。设该矩阵为M,那么M[j][k]即为从路口j到路口k之间不同路径的数量。矩阵M应该按行先顺序打印,矩阵的每行都独占一行输出。每个矩阵前都应由一行字符串“matrix for city k”作为开头(k表示给出的矩阵顺序编号,由0开始)。

If there are an infinite number of different paths between two intersections a -1 should be printed. DO NOT worry about justifying and aligning the output of each matrix. All entries in a row should be separated by whitespace.
如果在两个口之间存在无数量不同的路径,则应打印出-1。不要刻意的使矩阵对齐输出,行中的每个元素用空格隔开即可。

 

Sample Input
输入示例

7 0 1 0 2 0 4 2 4 2 3 3 1 4 3
5
0 2
0 1 1 5 2 5 2 1
9
0 1 0 2 0 3
0 4 1 4 2 1
2 0
3 0
3 1

 

Sample Output
输出示例

matrix for city 0
0 4 1 3 2
0 0 0 0 0
0 2 0 2 1
0 1 0 0 0
0 1 0 1 0
matrix for city 1
0 2 1 0 0 3
0 0 0 0 0 1
0 1 0 0 0 2
0 0 0 0 0 0
0 0 0 0 0 0
0 0 0 0 0 0
matrix for city 2
-1 -1 -1 -1 -1
0 0 0 0 1
-1 -1 -1 -1 -1
-1 -1 -1 -1 -1
0 0 0 0 0

 

Analysis
分析

有些难度题目,值得推荐。题目也非常有趣,建议您在仔细的读题并认真的思考后再来看分析和解答,最好能亲自动手做一下。

它看似需要用图论的某种算法来解决,事实上这道题的主要是靠深度遍例(DFS)。对于一个无环图来说,用深度遍例找出所有路径的算法是典型的,依靠临接表即可完成,但这道题的主要难度就在环上。要先找出所有的环,就得在递归的过程中记录由起点到达当前节点的路径。这样当进入下一级节点时,只需在路径中查找与之相同的节点,如果找到即可判定存在环。若下一级节点与当前路径构成环,就不能继续向下一级遍例,否则将进入死循环。如果没有构成环,就为起点到达当前节点的路径数量加1。

每当发现一个环时,应该将环上所有的路口都进行标记,因为由这些路口出发的所有路径都可以有无数条(至少可以先绕着环转一圈回到起点)。在深度遍例结束后就得到了所有在环上的路口以及到由起点路口到达各路口的路径数量。

这些路径中有些是不满足条件的,因为可能“穿过”一些环,现在就需要把这些路径找出来。显然,如果到达的路口就在环上,那么路径一定为无数条,而且,能通过这些环上的路口到达的其它路口的路径也为无数条。如果路径上所有的点都不在环上,则为满足条件的可达路径。

我想这道题应该还有别的更好的思路,还请各位老师给予指导。下面的代码比较长,也没有经过足够的优化,可能看起来会比较乱,望见谅!

 

Solution
解答

#include <algorithm>
#include <iostream>
#include <vector>
using namespace std;
//常量,表示可能出现的路口总数
const int nTotal = 30;
//用来存储结果的矩阵
int aMat[nTotal][nTotal];
//临接表
vector<int> AdjTbl[nTotal];
//最大路口编号
int nMaxIs;
//深度遍例,每次找出由路径最后一个路口可达的所有路口
void SearchRoad(vector<int> &Path) {
        //为加快运算,方便编码,为当前路口(路径末端)的邻接表设临时变量
        vector<int> &Adj = AdjTbl[Path.back()];
        //循环遍例当前路口的每一个相临路口
        for (vector<int>::iterator iTo = Adj.begin(), i; iTo != Adj.end(); ++iTo) {
                //从路径首个路口开始向后寻找可达路口,即检查是否已出现环
                for (i = Path.begin(); i != Path.end() && *i != *iTo; ++i);
                //如果找到环
                if (i != Path.end()) {
                        //环中每个路口到它临接的路口都有无数条路径
                        for (vector<int>::iterator j = i; j != Path.end(); ++j) {
                                aMat[*j][*j] = -1;
                        }
                        //继续下一个节点,避免对环进行深度遍例
                        continue;
                } //若与路径中任一个路口都不直接构成环,可继续向下一级遍例
                //并累计到达下一级路口的次数
                ++aMat[Path.front()][*iTo];
                //将下一级路口加入路径
                Path.push_back(*iTo);
                //继续向下执行深度遍例
                SearchRoad(Path);
                //本节点及其子树都遍例完毕,恢复原路径
                Path.pop_back();
        }
}
//主函数
int main(void) {
        //循环处理输入的每一个城市交通数据
        for (int nWayCnt, nCityCnt = 0; cin >> nWayCnt; ++nCityCnt) {
                //清空结果矩阵和临接表
                memset(aMat, 0, sizeof(aMat));
                for (int i = 0; i < nTotal; AdjTbl[i++].clear());
                nMaxIs = 0;
                //读入所有单行线数据,并统计出现的最大路口编号
                for (int i = 0, nFrom, nTo; i < nWayCnt; ++i) {
                        cin >> nFrom >> nTo;
                        AdjTbl[nFrom].push_back(nTo);
                        nMaxIs = max(max(nFrom, nTo), nMaxIs);
                }
                //查找从每个路口可到达的所有路口并发现所有在环上的路口
                for (int nRoot = 0; nRoot <= nMaxIs; ++nRoot) {
                        vector<int> Path(1, nRoot);
                        SearchRoad(Path);
                }
                //处理图中的环
                for (int i = 0; i <= nMaxIs; ++i) {
                        //如果路口本身就在环上,则所有可达路口都有无数条路径
                        if (aMat[i][i] == -1) {
                                //为其所有可达路口的路径赋值为-1
                                for (int j = 0; j <= nMaxIs; ++j) {
                                        aMat[i][j] = aMat[i][j] != 0 ? -1 : aMat[i][j];
                                }
                                continue;
                        }
                        //如果路口不在环上,则查找其所有可达路口是否在环上
                        for (int j = 0; j <= nMaxIs; ++j) {
                                //若可达路口j在环上,那么i至j有无数条路径
                                if (aMat[i][j] != 0 && aMat[j][j] == -1) {
                                        //显然j可达的所有路口k,i也可达,且路径同样无数
                                        for (int k = 0; k <= nMaxIs; ++k) {
                                                aMat[i][k] = (aMat[j][k] != 0) ? -1 : aMat[i][k];
                                        }
                                }
                        }
                }
                //按要求输出矩阵结果
                cout << "matrix for city " << nCityCnt << endl;
                for (int i = 0, j; i <= nMaxIs; ++i) {
                        //避免在行尾多出空格
                        for (j = 0; j < nMaxIs; cout << aMat[i][j++] << ' ');
                        cout << aMat[i][j] << endl;
                }
        }
        return 0;
}

作者: Devymex   发布时间: 2010-08-17