POJ 1135 Domino Effect

题我就没读懂。。。

其实是这样,把每一个key domino当做节点,他们之间的骨牌数目当作边权,用dijkstra处理一下得到的就是骨牌倒道该节点的最短时间,接下来我们要考虑在中间倒完的情况。

如果在两个节点中间倒完的话,也就意味着从起点经过两个key domino到终点,左右两边走过的时间是一样的,所以我们可以用(dis[a]+dis[b]+edge.cost)/2这个就可以求出在两点之间倒完需要多少时间,我们把每条边都这样过一遍,如果求出来的这个值比左右两边节点的dis都大,说明倒在中间,如果正好等于某一个节点的dis值,就把那个节点当作终点,把所有的边都这样过完之后,找到最大的(dis[a]+dis[b]+edge.cost)/2对应的终止情形,就是答案。

HDU 3023 Dirt

经过了极其艰辛(~3天)的过程终于把这个题肝出来了。

基本题型是最短路,采用dijkstra,利用优先队列做堆优化,之所以老不过是因为松弛写错了,当时错误地认为下一节点一定会换鞋,实际上应该判断一下是不是要换鞋,换鞋和不换鞋的松弛操作要分开进行。

ZOJ 3820 ACM 牡丹江 Building Fire Stations

SXBK的一个题(对我现在的水平来说)

其实是这样,对于一棵树,在上面建1个消防站,最优位置在直径中心,建立两个消防站呢?其实可以把整个大树分成两个小树,在每个小树自己的直径的中心建立消防站就搞定了,在分树的时候,如果发生分歧(如果直径有奇数个点,就会出现这种分歧情况,如果有偶数个节点,中央节点有两个,直接从两个节点的中央裂开就行,不会产生分歧),这时候我把中央节点上连接的那些节点中除了直径上的两点之外的那些节点都叫做中立节点,我们只要进行两次讨论,一次把这些中立节点分入左半树,求出这种分法中左右半树的直径,然后把左右直径的最大值当成接下来判定哪个方法更好的基准(因为我们要的是最长距离的最小值),然后再把这些中立节点分入右半树,搞出相应的解,接下来我们比较刚才保存下来的基准,哪个小要哪种方案,这样我们就把最终的解搞出来了。

我觉得其实这种思路还可以推广,如果建立3个,4个乃至n个,我们也可以切割树求直径来搞。

然后切树是,我们可以通过把某个点“屏蔽”,来实现不会让dfs进入另一半子树的目的。

其实这个题还可以通过枚举大直径上的两点求出最优解来解决,思路也是比较清晰的。

 

HDU 4607 Park Visit

树的直径就是最长路,求法其实很简单的,就是两遍dfs,第一遍dfs把终点的节点编号输出来,第二次用这个节点当成起点再来一遍,这回输出最高深度,就是直径。

这个直径用法就是,如果需要过的节点数少于直径长度,说明不用拐弯,沿着直径走就ok,如果需要过的节点数大于直径长度,说明要拐弯,拐弯进去还要再出来,相当于走了两遍,所以是直径长度加上二倍多出来的分支长度,调整一下就ok了。

Asia – Tokyo – 2007/2008 UVa 3887 Slim Span

在kruskal的基础上,每一次干掉一条边,再kruskal一次,如果得到了生成树,就看看他的最大边权-最小边权是不是可以更新当前最优解,然后反复这么搞把所有边都搞掉就完成遍历了,因为事先已经对边权排序了,这么做并不会丢解。

POJ 1679 The Unique MST

这个题我没想出来,一种解法是首先对于每条边确认是不是还有和这条边权值相同的其他边,如果有的话标记上,然后先来一遍裸的kruskal,把第一遍kruskal用到的节点全都标记上,然后开始扫,如果当前边既有同权的其他边又被在第一次kruskal中使用了了,就把这个边干掉再来一遍kruskal,看看这次生成树的总权值变没变,如果没有变就说明生成树不唯一,如果变了,就保持着这个边被干掉的状态接着扫,碰见符合要求的再干掉(保持着以前干掉的边被干掉的状态,用剩下的边尝试组生成树.如果你不判断有没有同权的边直接挨个把第一遍用过的边干掉,也可以。

RE了好多次然后换个编译器就过了,我也不知道是我抽了还是oj抽了,还有可能是.size()这个函数因为是返回unsigned long如果-1的话可能故障,以后要么强制类型转换,要么开个常数存一下。

POJ 1751 Highways

要是在POJ上做的话不要交C++会T,交G++就好了。

kruskal,没啥好说的。

HDU 1233 还是畅通工程

拿1232的代码改一下就好了。

HDU 1232 畅通工程

Kruskal算法,并查集老写挫,正确的写法是合并操作是把parentA和parentB合并(链接根节点),查询的时候压缩,就是

return parent_bcj[currNode] = query_bcj(parent_bcj[currNode]);

这句。

求有向图强连通分量的Tarjan算法

-_-#上面的ccs应该是scc,名字叫错了跪。。。

关于Tarjan,https://www.byvoid.com/blog/scc-tarjan这个讲的挺好,一定要看看。然后我说下我自己咋想的。

首先初始化,对于每个节点都要分配一个dfn值和low值,开一个tim当作计时器,然后基本的流程就是:

~进入当前节点
~tim++
~当前节点dfn=low=tim
~标记当前结点已经走过(注意图的遍历中节点只会走一次,也就是说标记不用撤销)
~把当前节点压入栈中(这个stack预先开好了)
~开始扫当前节点的分支,如果这个分支走过了,判断一下是不是这个分支是不是在栈里,如果在栈里的话就看看分支的dfn是不是比当前节点的low还小,小的话就把当前的low值调成那个dfn值;如果分支没走过,直接递归进到那个分支里去,等到递归出来之后,如果分支的low值比当前low值小,就令当前low值为分支low值(这步比较难懂)
~等到所有分支扫完,检查一下当前的dfn是不是等于low,如果不相等直接退出当前递归就好;相等的话,说明节点是scc的根,这个时候不断弹出栈,直到当前节点也被弹出,弹出的这些节点放在一起就是一组scc。

其实这个Tarjan的原理就是利用时间标记,进入栈的节点都是预定互相连通的,dfu值的意思就是当遍历到这个节点,是第几步(我把tim当作步来看),low的意思就是从当前节点可以走到的尽量在时间上先走到的栈内节点对应的dfu,就是最优解的对应标号,这个low肯定是越小越好,因为low越小我们的连通回路就越大。如果low=dfu,那就是当前个点已经没有办法和栈里的既有的点连通了,所以当尝试了全部的走法之后还是没有找到一个栈里的节点来和当前点连通,就说明这个连通回路闭合了,这个时候只要把整个连通回路统统从栈里弹出来,就能得到整个连通回路,也就是scc的一个。每步中接下来要走的节点有三种情况:有的节点访问过但是不在栈里,这样的点不用考虑,因为它已经被弹出了,相当于这个节点已经和我们没关系了;有的点访问过而且在栈里,这样的节点有可能就是当前连通回路的终点了,所以我们要比较一下当前low值是不是比它的dfu大,如果大的话说明这个点可以连通过去,如果小的话说明我们已经有一种连通方式可以包括和它连通了,这个不是最优解,我们就忽略好了;有的节点没访问过,这样的点我们要递归进去处理一下,递归完之后这个节点就有了它在当前情形下该有的dfu和low了,因为这个点和当前点连通,所以这个点要是有一个比当前更好的连通方案,我们用它的就好。

基本就是这么搞,这种时间流逝标记的方法很重要(虽然我还不知道到底有多重要。。。)。

 

Scroll to top