我们探索某个领域的知识,无不怀揣的核弹级的好奇心与求知欲,那么,今天,我们就将开始对图论的探索。
观察一副《机械迷城》 的一处谜题。
不得不承认,《机械迷城》这款解密游戏难度远胜于《纪念碑谷》, 其中一个困难点就在于——《纪念碑谷》的目标是很明确的,但是《机械迷城》往往需要自己凭感觉设立目标。而这里的关卡的目标,就是堵住第三个出水口。
为了解决这个谜题,如果不去考虑用暴力枚举的方法去试探(其实很多情况下都是用到这种情况)一开始,我们似乎会从模拟电路的角度来看待这个水管图,但是会发现它太过复杂,简单的电路图似乎很难以表达整个线路的构造,这里,我们会联想到一些能够表征点与点之间的关系的数学模型——那就是图论所擅长的领域。
“联系”或者“关系”,在自然界中就像任意两个物体之间的万有引力一样常见,而图论就是致力于将一个集合的个元素的相互关系给找出。
在图论中,用点指代“事物”,用边指代“事物间某种联系”,而在边上可以添加一种叫做“权值”的信息用以更加详细的表征这种联系,这就是图。
而基于图最基本的定义,会衍生出一些特殊的图譬如补图、二分图、完全图,这里暂且不去深究其定义。
那么我们有了强大的数学工具在手,再来解决上面这个谜题,就显得很小儿科了。我们从水的入口开始,顺着管道,分叉口作为图中的点,而水的流向以及是否有阀门则可以在点与点之间 边上体现。虽然画出其抽象图可能会比较麻烦,但是不难想象,如果从游戏开发人员的角度去看这个谜题,显然更加需要图论的应用。
通过上面的介绍,相信读者已经对图论有了很初步的了解了。那么我们开始结合具体的问题更加深入的探究图论知识的奥秘。
关于图的拓扑排序的问题。(Problem source:pku1128)
这里先非常粗略的给出图的拓扑排序的定义:即一个图的所有点排成一个序列,v1v2v3v4……,任取两点vi、vj,如果两者之间存在一条通路,那么一定是从vi -> vj , 那么此时我们说这是一个图的拓扑排序后的序列。
而拓扑排序在实现上其实也非常简单,我们遍历当前的图,找到一个没有入度(没有边进来)的点,作为拓扑序列的第一个点,一次类推,直到最后序列包含原图的所有点。
基于这种构造方法我们能够很好的理解——无向图、带环图是没有拓扑排序的。
题目大意:有五个已知的矩阵如上图所示,将它们堆叠在一起将形成一个新的矩阵。现在的问题是,给你一个堆叠后形成的矩阵,让你给出所有可以成立的堆叠顺序。
数理分析: 针对这个问题,选好思维出发的角度非常重要。根据题设的要求,我们能够保证每种字母框的每个边都会露出一个字母,那么凭借这个信息,我们就可以找到构成这个框的每个点在图中的位置。基于此,我们再去寻找在这个框上是否出现了其他字母,一旦出现,说明这个字母就在当前字母的上面。这样遍历下来,我们就可以将一个矩阵图,转化记载了各个字母相对位置的图——也就是我们所熟悉的点与点之间连接着线(有向)的那种抽象的图,做到了这一步,我们将构造出来的图和题意进行比较——出现的先后顺序,其实就表征了图中的有向性,而这其实也是拓扑排序所体现的,所以我们自然而然的要开始进行拓扑排序了。
这里题目的要求是给出所有的拓扑排序方案,结合上面构造某种拓扑排序的简单方法,再加上遍历图的一种方法——深搜,我们便可以找到所有的拓扑排序。
编程实现:图论相对来数在数理思维上并不是那么困难,而在编程实现上则比较困难。这里的困难点其实就体现在,同一个图的转化导致的不同信息呈现——这里就是一个具象的矩阵图转化成表征各个字母出现的前后关系的抽象图。有了这一步关键的过渡后,只需构造深度优先搜索把所有的情况遍历出来即可。
参考代码如下。(暂时还没AC)
#include <stdio.h> #include <string.h> #define maxn 35 #define maxm 35 #define kind 5 char ori[maxn][maxn], ans[kind + 1]; int m, n, in[kind], total; bool map[kind][kind]; struct Node { int x, y; } lt[maxm], rb[maxm]; //这里采用记录一个边框左上角的点和右下角的点的数据用以后面扫描边框上的其他字母. //因此初始化的时候对应着,左上角的点应该尽量往右下角初始化,右下角的点往左上角初始化。 void GetMap() { int i, j, t, k, x, y; memset(map , 0 , sizeof(map)); memset(in , -1 , sizeof(in)); memset(lt , 0x3f, sizeof(lt)); memset(rb , -1 , sizeof(rb)); for(i = total = 0; i < n; i++) for(j = 0; j < m; j++) { if(ori[i][j] == '.') continue; t = ori[i][j] - 'A'; if(in[t] == -1) { in[t] = 0; total++; } if(i < lt[t].x) lt[t].x = i; if(i > rb[t].x) rb[t].x = i; if(lt[t].y > j) lt[t].y = j; if(rb[t].y < j) rb[t].y = j; } for(i = 0; i < kind; i++) { for(x = lt[i].x; x <= rb[i].x; ++x) for(y = lt[i].y; y <= rb[i].y; ++y){ if(x > lt[i].x && y > lt[i].y && x < rb[i].x && y < rb[i].y) continue; t = ori[x][y] - 'A'; if(t != i && !map[i][t]){ map[i][t] = true; in[t]++; } } } } void DFS(int id) //fantastic! { if(id == total){ ans[id] = '