zoukankan      html  css  js  c++  java
  • 【题解】洛谷P3687 BZOJ4784 【[ZJOI2017]仙人掌】

    整个算法范围两部分:判断仙人掌与得出答案

    Part 1 判断仙人掌

    对于一个无自环无重边的无向连通图,方案数非 (0) 当且仅当这个图本身满足仙人掌的性质(即:任意一条边最多属于一个简单环)。

    因此我们需要判断仙人掌。

    注意这道题的特殊性,你会发现,一个简单环对答案是无贡献的(原因请看 Part 2)。

    因此我们不需要存储每个环包括多少条边,以及包含的边是什么。我们只需要对处于简单环中的边打上删除标记即可

    因此,在仙人掌判断中,
    ({你不需要 tarjan, 或者树上差分,你只需要暴力})
    解释 在 dfs 中,当你发现这条边指向的节点不是父亲,但却已经被访问过,设这条边为 (u-v),那么 (u-v-LCA(u,v)-u) 构成一个环,你只需要用类似暴力求LCA的方法一层层往上爬,并对爬到的边打标记即可。不要忘了给 (u-v) 打上删除标记。

    正确性 一条边至多只可能被打一次的删除标记。如果被打第二次,那么可以判断这条边至少处于两个简单环中,因此立即返回并输出 (0)(无解)。

    注意,
    ({1. 你最好需要邻接表存图。})
    解释 因为若你对一条边 u-v 打标记,你肯定也要对 v-u 打标记。使用编号从 (0) 开始的邻接表时,如果要对编号为 (e) 的边打标记,对 (lfloorfrac{e}{2} floor) 打标记即可。使用 (vector) 存图会麻烦很多。
    ({2. 你最好需要存,从父亲节点到自己的边的编号。})
    解释 因为在暴力对一个环上的边打标记时,存下这条“到父边”(姑且让我用这个名词吧)可以保证跳到父亲的复杂度为 (Theta(1)) 的。不存这条边,只能暴力搜索出边,均摊复杂度 (Theta(frac{m}{n}))。(当然,你要跟我说最好情况都是 (Omega(1)),我也没办法。但是出题人大概率不会这么良心。)
    ({3. 一旦发现当前图不是仙人掌,立即返回。})
    解释 如果等到搜索结束再返回,复杂度可以被卡到最坏 (O(n^2))

    Part 2 求解仙人掌

    现在我们已经确定原图为仙人掌(非仙人掌右转 puts("0")),并且对环上的边打上了删除标记。

    ({Q:}) 为什么“环上的边对答案无贡献”?
    ({A:}) 显然,环上的边已经处于一个简单环之中。因此根据仙人掌的定义,我们不能把这些“环上的边”包含到其他简单环之中。因此这些边不可能对答案有贡献。

    引理 考虑到原图已经被我们划分成了一些树,而且这些树之间是不会互相影响的。
    证明 注意到,如果你想对两棵树上的两个节点连边,由于原图是无向连通图,因此这两棵树原来肯定被一条环上的链所连接。因此,若要对这两棵树上的这两个节点连边,原来环上的链就会被覆盖 (2) 次,不符合仙人掌定义。

    因此,我们可以对每棵树单独求解,然后将答案利用乘法原理乘起来即可。


    考虑现在我们达到了什么。
    我们成功将一道 仙人掌 上的问题转化成了 上的问题(部分分 #6~#7)


    考虑设计 (DP)

    先把辅助数组 (h) 讲了。
    (h[i]) 表示(i) 个点,它们之间匹配的方案数
    简单来讲,现在有 (i) 个点,都是某个节点 (u) 的孩子,而且这 (i) 个节点各自到 (u) 的边都没有被简单环覆盖(易知:(i=operatorname{size_{Children_u}})
    考虑到如果连一条边 (v-w) 那么 (v-u)(w-u) 这两条边都会被覆盖。
    故:一个点 (v) 最多只能与它的兄弟连一条边。

    由于 (h) 值与树的形态无关,可以预处理。
    假设已有 (i - 1) 个点,现在加入 (1) 个点。
    这个点可以不与任何兄弟连边,方案数为 (h[i - 1])
    这个点可以选择与一个兄弟连边,有 (i - 1) 种选择,每种选择带来 (h[i - 2]) 的贡献,方案数 ((i - 1)·h[i - 2])

    所以,

    [h[i] = h[i - 1] + (i - 1)·h[i - 2] ]

    ({这时候你要想清楚这个状态为什么设计。})

    设计状态 (g[u]) 表示节点 (u) 可向上拓展 的方案数。

    考虑 (g[u]) 的转移。
    (g[u]) 的意义,我们需要在 (ucupoperatorname{Children_u}) 中选出一个节点,作为接口节点,可以同父亲的其他直接孩子的子树节点连接。
    因为 (u-fa[u]) 这条边最多只能属于一个简单环,所以保留一个接口节点即可。

    我们有

    [g[u] = h[Deg + 1] · prod_{vin operatorname{Children_u} }g[v] ]

    其中 (Deg) 表示 (operatorname{size_{Children_u}})

    注意到,此时我们将节点 (u) 自身也作为 (operatorname{Children_u}) 的一个兄弟来计算。关于这点,理解方式有二:

    1. (g[u] = h[Deg] · prod_{vin operatorname{Children_u} }g[v] + Deg · h[Deg - 1] · prod_{vin operatorname{Children_u} }g[v]= exttt{原式})
      如果把 (u) 自身作为接口节点,方案数为加号左边,不解释;
      如果把 (u) 的一个孩子作为接口节点,方案数为加号右边。
      • 解释:选择一个孩子((Deg))剩下的任意匹配($h[Deg - 1] $)再乘上孩子的 (g) 值((prod_{vin operatorname{Children_u} }g[v])
    2. 如果在 (h[Deg + 1]) 种匹配中,
      • (u) 被连边:假设 (u-v) 被连,那么这条边的意义不是连接,而是选定 (v) 作为接口节点
      • (u) 未被连边:(u)接口节点

    然而,
    ({答案不是 g[root]})

    考虑到在 (g) 的转移中,有可能 (u-v) 被连 ((vin operatorname{Children_u}))。这时,按照我们的理解,(v) 被选中作为接口节点。
    但是 (root) 节点不再向上延伸,(root) 节点不需要接口节点,接口节点的存在只会导致答案重复统计
    (g) 的转移中,我们确实重复统计了一些边的贡献,但是这“重复统计”的前提是“接口节点”
    意即:(g[u])(fa[u]) 所统计,才是“重复统计”的正确性之源。

    ({对于一棵树,答案是 }h[Deg_{root}] · prod_{vin operatorname{Children_{root}} }g[v])

    这样不会重复统计。
    这也就是其他很多题解中提到的 (f) 数组。

    代码

    见 gitee 仓库 https://gitee.com/hkxa/mycode/blob/master/luogu/P3687.cpp

  • 相关阅读:
    bzoj 1025: [SCOI2009]游戏【数学+dp】
    bzoj 1195: [HNOI2006]最短母串【状压dp】
    洛谷 P1083 借教室【二分+差分/线段树】
    bzoj 2151: 种树【贪心+堆】
    bzoj 1055: [HAOI2008]玩具取名【区间dp】
    bzoj 2152: 聪聪可可【点分治】
    bzoj 4552: [Tjoi2016&Heoi2016]排序【二分+线段树】
    bzoj 1103: [POI2007]大都市meg【dfs序+树状数组】
    bzoj 3751: [NOIP2014]解方程【数学】
    bzoj 3612: [Heoi2014]平衡【整数划分dp】
  • 原文地址:https://www.cnblogs.com/hkxadpall/p/solution-luogu-P3687.html
Copyright © 2011-2022 走看看