Day4
{第二天的测试终于有点感觉了,第一题是模拟,第二题是动归,第三道是可修改边的最小生成树(题目叙述各种坑,大家都没看出来是生成树,题解上给出来的都是超时算法,标算应该是动态树吧,各种不会写啊。。。。)}
还是先放一下题目(第二题的)吧。
一个简单图是一个无向图,图中没有自环边,也没有重边,重边是指某两条边的两端顶点均相同。给你一个简单图,图中每个顶点都有一个权值,每一条边都至多属于一个简单圈,你的任务是找出这样一个顶点的集合,集合中任意两个顶点间不相邻,并且集合中顶点的权值和最大。
输入格式:
第一行为一个整数n(1<=n<=10000),表示顶点的个数。
第二行有n个整数,(0<=每个整数<10000),第i个数表示第i个顶点的权值。
第三行有一个整数m,表示边数。
接下来的m行,每行有两个整数a,b(1<=a,b<=n),表示在顶点a,b之间有一条边。
输出格式:
对于每一个测试数据,输出一个整数,表示权值最大的独立集的权值和。
样例:
subseta.in
5
1 2 1 2 0
6
1 2
2 3
3 1
2 4
4 5
5 2
subseta.out
3
先分析一下题目,如果不考虑题中的“每一条边都至多属于一个简单圈”条件,那么这是一个经典的NP问题,是有经典算法的,但对于本题的数据范围是不可行的。
所以重点就是条件给我们带来了什么便利,仔细分析后图大致是这样的:
其中每一条边不是再环中就是在树上,所以可以考虑用动态规划来解决,那如何解决在图上的动规呢?我们知道动归有一个条件就是问题具有拓扑结构。根据这点,我们将图划分,因为题中出现了很多环,而他们的交集只有点,因此我们考虑环上的动态规划,要求是不可选相邻点条件下的的最大权值。我们可以将环分为链来动归,但问题是在于,无法控制第一个点和最后一个点的状态不同(选和不选)。所以我们就要分情况来讨论,第一个点选和不选,这样动归的结果存放在最后一个点中。
环搞定了,但怎么扩展到图上呢?考虑图中的环的交点,他们其实就是图中的割点。这样以割点来划分环,先将最下面的环动归后,将结果存在割点中,以便用于下一个环的计算,这样就可以搞定了。树的情况可以讲两个点看做一个小环计算,也可以做树形动归,但这样判断树会很悲剧的。
{接下来是算法时间。。。}
今天的重点是线段树中的Lazy-Tag,这是一个很有爱的想法。我们在做线段树时总是要面临线段修改的问题,而我们又不能递归更改线段树中的包含该线段所有线段,这样我们的想法就是做一个标记,我们先不修改它,而放在后面用到时再改。也就是说我们明明用不到线段中的信息我们还要傻傻地修改它,这是很悲剧的。
而看起来这是一个简单的算法,但实际上是有很多技巧的。我们先看一个简单的情况。
维护一个序列,可以完成如下操作:
INCREASE L R Num 表示将L到R的数都加上Num
DECREASE L R Num 表示将L到R的数都减去Num
SUM L R 表示对L到R求和
MIN L R 表示L到R的最小值
题目中要求的操作求和和最小值是线段树的基本操作而线段修改则要用到标记了。维护了两个操作但其实际上是一个操作,维护一个标记(add)就可解决问题。实现很简单只要注意标记清空和下传的实际即可。
不要认为标记就这一点东西,我们在加上一个操作来看看。
MULTIPLY L R Num表示将L到R的书都乘以Num。
这个操作用add标记显然不可维护,那我们只能在增加一个标记了。我们增加fac表示改线段要乘的数。但是问题来了,我们如何完成双标记的清空和下传呢?
我们先考虑是否可以让两标记兼容,意思是用fac[k]*value[k]+add[k]来表示当前线段要更改成的数,那么清空就变得简单了,现在考虑标记的下传。如果fac[k]=1那么就表示只用下传add[k]即可,也就是使add[leftchild[k]]+add[k]和add[rightchild[k]]+add[k](以下用child[k]表示k的两个孩子)就行了。而如果fac[k]<>1那么使
fac[child[k]]*fac[k],add[child[k]]*fac[k]+add[k]就行了。
再来我们再加一个操作:
CHANGE L R Num 表示将L到R的数均变为Num。
这时我们再考虑一下怎么来搞呢?先是来考虑我们能不能用刚才的标记来解决,我们发现将fac[k]设为0,将add[k]设为Num即可实现CHANGE的操作。
呃呃呃。。。这个有点取巧了,况且是建立在fac[k]的基础上的。如果题中没有乘的操作,我们想到这个就不太可能了吧。。。(没事谁会想到加一个并没出现的操作来实现要实现的操作呢,当然你想到了我也没有办法。。。)
下面考虑不用fac来实现CHANGE操作,我们自然想到要用一个标记来记录将当前线段变为什么,记为cover[k]吧。那么考虑怎样实现add和cover的双标记下传,我们发现刚刚的标记兼容法已经不管用了,也就是说我们无法用cover和add组合出当前线段的修改情况。而我们又注意到,不管原来线段上有没有add标记在经过CHANGE操作后都会使其上的add标记清空,因此一个线段上同时最多只存在一个标记。那么这就启发我们思考标记的清空及下传方法。
这就用到了第二种处理多标记下传的方法,确定操作的优先级,然后尽量使一个结点上只有一个标记。这样问题就会简化很多。即若当前节点上含有cover标记那么下传时不管孩子节点的标记是什么样的我们都要清空add[child[k]]标记,将cover[child[k]]标记变为cover[k]。而当当前节点含有add标记时,若孩子节点有cover标记,那么就在cover[child[k]]上加上add[k],否则就在add[child[k]]上加上add[k]。
此时我们已经考虑了两种情况并提出了解决方案,但题目总是无止境的。如果两种情况均不完全符合,那么就要分情况来讨论了,这才是标记问题的最难的部分。
但不管怎样我们始终在解决的问题都是如何高效而正确地实现标记的清空(标记域与节点值域的合并),以及高效而正确地实现标记的下传(标记域与标记域的合并)。而这样的问题就是我们要想办法处理的问题。
【本文中的内容参考了博友Master_Chivu的博客,他比我写的要好,看了之后深受启发,大家可以移步到
这里http://www.cnblogs.com/Booble/archive/2010/10/11/1847793.html
和这里http://www.cnblogs.com/Booble/archive/2011/06/04/2072724.html】
【欢迎大家指出文中的不足,我们再作进一步的讨论,如有错误我会及时修改】
【Snow Dancer原创,转载请注明,谢谢】