我的OI心得(二)之 图论(一)

来源:互联网 发布:串口数据采集实时显示 编辑:程序博客网 时间:2024/03/29 15:40

树与DFS

树是一种特殊的图。一般的树有一个根节点,这个点是亘古以来就有的(盘古大神!!)。他会生出很多子节点,用一条(有向,无向,都可以,看需要)边相连。各子节点相互称“兄弟节点”,尊直接上司为“父节点”,父节点的父节点及以上称“祖宗节点”,唯一不好称呼的是。。。比如父节点的父节点的另一个子节点——叔伯节点??

显然除了根节点外每个节点都有且仅有一个父节点。除了叶子节点外,每个节点都有一或若干个子节点。

树一般看需要有两种存法。

一,父亲表示法。就是开个数组a,对于下标i表示节点编号,a[i]表示其父亲的编号。合适的话异常好用。比如树状背包问题,可以和拓扑排序一起用。后话。

二,邻接表。就是“儿子表”。对于i,依次存i的儿子。比较常用。

 

二叉树比较特殊,就是每个节点的儿子不会多于两个。这很好啊,存起来方便多了。。。事实上,NOIP我们常用到完全二叉树,因为他异常好存。完全二叉树就是这样的:对于一个二叉树,我们以这样的方式来编号:

看出来了么?从上到下,从左到右地编号。如果编号是从1开始而且连续的话,比如这个,就是完全二叉树。存?很简单,开个数组,下标是编号,存权(一般问题中的树都是节点存权)。父子关系很简单:对于节点i,显然2*i就是其左子节点,2*i+1就是其右子节点。

树是用来干嘛的?树可以用来遍历,可以用来做堆,查找,排序。

 

先说遍历。这就不得不说搜索了。

 

咳咳!!慢慢说。

图是一种模型。我们,作为新一代的乖孩子,要懂得建模。图论建模,我们要搞清楚一件事:状态!什么是状态?

举个例子。有个八皇后问题(回溯的典型。。):在8*8的棋盘上放置8个皇后,使之两两不能互相攻击,打印所有方案。(皇后可以攻击自己所在的行、列、和两条长长的对角线)

这个情景的状态可以这么来描述:

开个数组a:array[1..8]of integer;

a[i]表示第i行第a[i]列有个皇后,若a[i]=0,则第i行没有皇后。显然每行至多一个皇后,所以只需唯一的a[i]来记录。那么这个数组确实描述了棋盘的一种状态。这样看来,只要我们精确描述了一种状态,我们就能确定模型中的情形。若我们记录了a数组,我们相当于记录了棋盘上的情形。

做事要一步步来。我们可以给棋盘划分阶段,从初始到结束。怎么划分呢?

刚开始,棋盘为空,不妨称为阶段0

然后我们在第一行某列塞了一个皇后,我们认为达到了阶段1

然后我们在第二行某列塞了一个皇后,我们认为达到了阶段2

。。。

这个“阶段i”也有助于描述状态,但是开的数组a包含了这个信息,即我们可以从数组a推出阶段i。我们要解决问题,就要从简单的阶段入手,最后达到圆满的阶段,即阶段8。很幸运,我们可以用一个节点表示一个状态,其权值为数组a。什么是边呢?如果某阶段v1能达到某阶段v2,则我们可以从v1连一条有向边到v2。很幸运,这是一棵树!根节点是阶段0。他有8个子节点,分别表示第一行第某列塞了一个皇后。这八个子节点也各自有自己的自己点,但不一定是8个:题目说皇后间不能相互攻击——这样就只有5个或6个,因为第一行的两端的两个位置放上皇后,可以攻击到第二行2个位置;若第一行的皇后不在两端,则可以攻击第二行的3个位置。

反正就这么下去我们构造出了一个连根节点一共9层的树。我们要输出的就是所有叶子节点的权值。

我们要想办法访问所有的叶子节点。而现在我们现在只知道根节点。想想数学归纳法(不懂也不要紧。。。)!!如果我们知道某个节点,又知道如何用这个节点算出(也成为决策,就是通往儿子的路)他的儿子节点,则可以一层一层算下午去。按什么顺序算呢?就这个题目而言,较好的是深度优先遍历(不知道大家打过《仙剑》没有,那里面的迷宫大多数都是树状的,而玩家会一个一个搜出口,此路不通就回头,这就是典型的深度优先遍历——不适用于《仙剑3》的锁妖塔第三层),这样我们认为可以控制一个小人,从根节点出发,往下走到某个儿子处,看一下,再往下走,再看一下。到叶子节点i后,输出这个叶子节点i,然后回头朝那个叶子i的父亲那边跑,跑到后再去i的兄弟j那儿,再去j的兄弟(也就是i的兄弟)k那儿。。。直到把i所有的兄弟都问候一遍。然后回到i的爷爷处,开始问候i的叔叔及其儿子。。。

要保存每个节点的状态开销太大,我们可以用一个全局数组a来控制:从i他爸到i时我在a里面多塞一个皇后;从i回到i他爸时把这个皇后给拔出来。

那么如何决策呢?就是说,现在知道一个a,也知道现在是阶段几,那如何放下一个皇后可以保证到现在为止满足 互不攻击 这个条件呢?这里扯到一个小技巧:我们做四条布尔数组,flag1[i]记录第i行是否被皇后占领,flag2[i]记录第i列是否被占领,flag3[i]记录第i条某个方向的对角线是否被占领,flag4[i]记录第i条另一个方向的对照线是否被占领。有了这个记录,并在放皇后的时候适当修改以保持正确性,就能提高效率。

这个又叫“回溯法”加剪枝,可以用递归来写。

 

大概就是这样的:

procedure visit(depth:intetger);{depth表示准备到阶段几}

var i:integer;

begin

if depth=9 then begin 输出方案;exit;end;

       for i=1 to 8 do

       if 这一行第i列放一个皇后不与前面冲突(用4flag判断) then

              begin

                     a[depth]:=i;

                     4flag挑好位置赋值为true

                     visit(depth+1);

a[depth]:=0;{其实这一行也可以不要,因为会被直接覆盖,但是加上后更为严谨。}

4flag本次赋值的地方复原为false,这是必须的,因为你还要试探别的决策。

              end;

end;

      

主程序则调用visit(1),表示试探放第一个皇后。

 

这个是深搜,叫DFS,很常用!

原创粉丝点击