zoukankan      html  css  js  c++  java
  • DP&图论 DAY 2 下午

    DP&图论  DAY 2  下午

    基础树形DP

    前言
    1:与树或图的生成树相关的动态规划。
    2:以每棵子树为子结构,在父亲节点合并,注意树具有天然的子结构。
    这是很优美的很利于dp的。
    3:巧妙利用BfsDfs序,可以优化问题,或得到好的解决方法。
    4:可以与树上的数据结构相结合。
    5:树形Dp的时间复杂度要认真计算,部分问题可以均摊复杂度分析。
    6:一般设f[u]表示u子树的最优价值或者是说方案数。
    或者f[u][k]表示u子树附加信息为k的最优值,往往是通过考虑子树根节点
    的情况进行转移。

    7:树形dp,在树结构上,求最优值,求方案等dp问题,就可以考虑是树
    dp
    当然也有可能是点分治或者是分析性质的贪心题。但是树形dp绝对是一
    个很好的思考方向。

     

    >树上最大独立集

    给你一棵大小为n的树,求这棵树的最大独立集是多少。
    最大独立集指的是一个最大的点集使得其中的点两两没有边相连。
    N<=100000

    >Solution

    dp[i][0/1]表示做完了i的子树, i点是否选的最大独立集,即可直接转移。

    对于当前节点 i ,如果不选 i ,那么他的儿子可以选也可以不选

    如果选 i ,那么他的儿子一定不能选

    代码还是很好写的。


    >树的直径
    给你一颗点数为n的树,让你求这棵树的直径是多少,也就是求最长的两
    个点之间的距离。
    N<=100000
    >Solution

    1:设 f[i] 表示 这个点到子树里面的最远点是多远的,然后对于每一个点u
    求出以这个点为根的最远路径距离,直接找 {f[son_i]+edge_i} 的前两大
    值加起来即可。然后再在每一个点构成的答案里面取最大值就是全局的
    最优值了。

     

    2:随便找一个点bfs求它的最远点,设为x,再从x跑一遍bfs,求x最远点y
    (x,y)就是一个直径了。
    考虑离每一个点最远的点肯定是直径的其中一个端点。

     其他的一些简单问题
    1:一棵无向树,结点为n(<=10,000),删除哪些结点可以使得新图中每一
    棵树结点数小于等于n/2。也就是求重心。
    2:树的覆盖集,求最少选几个点能覆盖所有边,也就是不存在一条边两
    边点都没被选。(本质?)
    3:最大权独立集?
    其实都是一样的。

    覆盖集和独立集是互补的,一起构成了树的全集

    >Tree chain problem
    给定一棵有n 个点的树,以及m 条树链,其中第i 条树链的价值为wi,请
    选择一些没有公共点的树链,使得价值和最大。
    1:n,m<=1000

    >Solution

    考虑树形DP,设f(x)为以x为根的子树内选取不相交树链的价值和的最大值,
    枚举一条LCAx 的链(u,v,w),那么当前方案的价值为w+ 去除u v 路径上
    的点后深度最小的点的f的和。
    复杂度O(M*N)
    树链剖分优化可以做到O(M*log(n)^2)

    对于当前点 i ,如果 i 不在树链上,那么树链就在他的子树里面

    如果 i 在树链上,如图紫色部分,那么我们还可以在绿色部分找到别的树链

    >BZOJ1864 三色二叉树
    给出了一棵二叉树,点数为n,然后要求每个点和其左儿子和其右儿子三
    者两两之间颜色互不相同,求最多能有多少点被染成绿色。
    N<=10^5

    >Solution

     f[i][0] f[i][1]f[i][2]分别表示根是绿红蓝三种颜色时的最多/最少绿色的
    数量,转移的时候只要保证上面的约束就行,并不难。

     

    bzoj2466
    图论中的树为一个无环的无向图。给定一棵树,每个节点有一盏指示灯
     和一个按钮。如果节点的按扭被按了,那么该节点的灯会从熄灭变为点
     亮(当按之前是熄灭的),或者从点亮到熄灭(当按之前是点亮的)。
     并且该节点的直接邻居也发生同样的变化。
     开始的时候,所有的指示灯都是熄灭的。请编程计算最少要按多少次按
     钮,才能让所有节点的指示灯变为点亮状态。
    对于100%的数据,满足1 <= n <=100

    >Solution

    f[u][0/1][0/1]  自己亮没亮  能不能按灯

    f[u][0][0]=sigema f[j][1] [ 0 ](按了偶数次

    g[0]+f[i][0][0] --> g[0] 偶数个孩子按,最少操作次数

          +f[i][1][1] --> g[1] 奇数个孩子按,最少操作次数

    g[1]+f[i][1][1] --> g[0]

          +f[i][1][0] --> g[1]

    状态:
    f[pos][bool] 表示按下这个pos点的按钮之后,这个点亮(或不亮),它的所
    有子孙都亮的最小代价。
    g[pos][bool]表示不按这个点的按钮,这个点亮(不亮),它的所有子孙都亮
    的最小代价。

    树上背包

    >树上背包简化版
    给出一棵n个点的有根树,每个节点都是一个物品,具有价值Vi,如果一
    个物品要被选择,那么它的父亲必须被选择。
    求选择至多m个物品的最大价值和。
    n<=1000
    >Solution

    f[i][j]表示在以i为根子树中选择, i强制选择,选择j个点的最大价值,转移
    时每次将一个孩子暴力合并到父亲上,合并就枚举这个孩子内部选择了
    多少点即可。
    F[i][j]=max{f[i][j-k]+f[son][k] |k=0…(j-1)},就是枚举son内选了多少点。
    我们按照一般的分析复杂度的方式的话是:状态数N^2*转移复杂度N,总
    复杂度是O(N^3)
    实际上我们考虑每次合并的时候相当于是一组点对数量的复杂度,总的
    来看的话就是n个点点对的数量,均摊复杂度O(N^2)

    >bzoj4033: 树上染色
    有一棵点数为N的树,树边有边权。给你一个在0~N之内的正整数K,你要
    在这棵树中选择K个点,将其染成黑色,并
    将其他的N-K个点染成白色。将所有点染色后,你会获得黑点两两之间的
    距离加上白点两两之间距离的和的收益。
    问收益最大值是多少。
    N<=1000
    >Solution

    f[u][j]u子树j个黑点  u子树的最大收益  ,  j*(k-j) 

    考虑边的贡献:两端黑点数量的乘积+两端白点数量的乘积。
    定义状态f[i][j]表示i号节点为根节点的子树里面有j个黑色节点时最大的贡
    献值。
    然后我们要知道的就是i到其父亲这条边会计算次数就是: 子树中白色节
    点数子树外白色节点数+子树中黑色节点数子树外黑色节点数。

    这条边的贡献和i的各个孩子的子树内各有多少黑点是无关的,所以我们
    可以做背包求出来每个点子树内有j个黑点时贡献和是多少。
    代码如下 红线部分必须按图示这个写法,保证N^2的均摊复杂度分析

     

    >树上背包
    给出一棵n个点的有根树,每个节点都是一个物品,具有价值Vi和重量Wi
    如果一个物品要被选择,那么它的父亲必须被选择。
    求限制重量m内的最大价值和。
    n<=1000,m<=1000
    >Solution

    >最朴素的做法
    这里不是选点的数量而是重量,所以这里的朴素做法是O(n^3)
    f[i][j]表示在以i为根子树中选择, i强制选择,重量为j的最大价值,转移时
    每次将一个孩子暴力合并到父亲上,合并就枚举这个孩子内部选择了多
    少的重量即可。
    F[i][j]=max{f[i][j-k]+f[son][k] |k=0…(j-1)},就是枚举son内用了多少重量。
    注意我们这里两个一维数组的背包合并是n^2的,所以慢。
    但一个一维数组和一个单独的物品合并是线性的。

    >DFS序上做DP
    dfs序上Dp,如果不选一个点,则跳过代表他的子树的dfs上连续一段。
    f[i][j]表示dfs序上第i个点到第n个点,选了j的重量得到的最大的价值是
    多少。 i可以选也可以不选。不选的话就要跳过整个子树。
    T[i]表示dfs序为i的点标号。
    不选: f[i + size[ T[i] ] ][j]
    选: f[i+1][ j- w[ T[i] ]]+v[ T[i] ]
    两种情况取最大值即可。

    >另一个奇妙的方法
    不是每次将孩子与自己合并,我们直接把dp数组复制再传给孩子,再从
    孩子递归下去,最后原来自己的Dp数组和孩子的Dp数组直接在对应重量
    的价值中取max
    以下是步骤:
    我们现在在u节点,对u节点的dp数组中加入u点的物品。
    dp[i]=dp[i-w[u]]+v[u]操作,表示强制加入了u这个物品。
    Dpson数组=dp数组。
    递归计算sondp值,传入的参数是dpson数组。
    回溯回u
    对每一个idp[i] = max{dpson[i],dp[i]}

    >bzoj5123
    求一棵 [1,n] 的线段树的最大匹配数目与方案数。
    N<=10^18

    f[u][0]=max(f[l][0],f[l][1])+max(f[r][0],f[r][1])

    树形dp+记忆化搜索
    f[l][r] 表示根节点为 [l,r] 的线段树,匹配选择根节点的最大匹配&方案
    数, g[l][r] 表示根节点为 [l,r] 的线段树,匹配不选择根节点的最大匹配&
    方案数。那么这是一个很普通的树形dp
    注意到区间长度相等的线段树的结果是一样的,且每层至多有两种区间
    长度不同的区间(打表或者推推式子都行),因此直接以区间长度为状态进
    行记忆化搜索即可。

     基环树

     >基环树
    基环树,也是环套树,简单地讲就是树上再加一条边。
    如果把那个环视为中心,可看成:有一个环,环上每个点都有一棵子树的形式。
    因此,对基环树的处理两部分分别为对树处理和对环处理。

    >基环树问题处理方法
    处理方法有
    1:断开环上一条边,枚举边端点的情况,然后当树来处理。
    2:先把环上挂着的树的信息都计算完,然后转化为序列问题,或者说是环形的序列问题。 


     >dfs找环
    基环树,环是关键,所以做这类题目我们首先得找到环。
    找环的方式很多,这里讲解dfs找环。
    对于dfs找环,我们就对这个基环树做dfs遍历。

    我们知道对于一个在图,它dfs树上的每一个返祖边(v-->u)
    dfs树上构成的路径就会构成一个环。也就是我们只需要找到这个返祖边即可。


    主函数调用时,要枚举每一个点。

    因为有可能是个基环树森林。

    这是很容易犯的一个坑: n个点n条边不一定是个基环树,准确来讲是基环树森林!! 


    如果说我们要采用断开一条边,当成树来处理。我们不需要找出来整
    个环,只需要找一个在环上的边,按下图写法会简便很多。 

    环的一个边的两个结点是 rt1 , rt2

    >基环内向树

    首先它是一个有向图,它构成类似基环树的结构,有一个特点是每个点
    都有且只有一个出度,并且环外的节点方向指向环内。
    如果题目说满足每一个点都有一个唯一出度,则本质上就是给了我们一个基环内向树
    森林(不只是一个基环内向树!!!!)
    任何一个点沿着唯一出边走都会走到环上
    利用这个性质可以随便选一个点再通过一
    个简单循环找到基环树的环。

     >基环外向树
    与基环内向树相反,它有且只有一个入度(基环内向树是出度),并且
    并且由环指向环外。
    可以把所有边反向后,变成基环内向树找快速找环。

    >bzoj1040
    N个人,每个人都有一个战斗力和一个讨厌的人(不是他本身),要求一
    个总战斗力最大的人的集合,满足集合内部两两不互相讨厌
    N<=10^5
    >Solution

    基环树的树上最大独立集

     把这个讨厌关系的图画出来,就是个基环内向树森林,然后我们要求最
    大权独立集。(当然也可以是外向树,关键看咋连边)
    求最大独立集内向和外向和无向图毫无区别,都是相邻的不能选。
    这里的基环树上有且仅有一个环,就是从任意环上一条边(u,v)断开环,分
    两种情况,一种是选u,不选v,一种是选v,不选u,两种情况取最大值。
    转化成树的话,就是那个简单的树形dp
    找环dfs找就好,或者从一个点顺着父亲一直走直到走到一个曾经走到过
    的点就找到一个环了。



     >BZOJ1791
    求无向基环森林中的每棵基环树的直径之和。
    点数n<=10^6

    >Solution

     

    qwq: 要统计森林的直径和要找每一个基环树的直径
    每一个基环树最大直径可能过环可能不过环
    过环的话找每个环端点上的树的最长分支之后处理找最长
    不过环的话每个端点上的树求树的最长直径

    先找出环,很明显答案有两种情况。
    1:在以一个环上节点为根的向外的树的直径。
    2:以两个环上节点分别为根的最大深度再加上两个节点在环上距离。
    第一种情况就是之前讲的树形dp
    第二种情况要处理出以环上每个节点为根的最大深度d[i],环上的点重标
    号,选环上1号点作为基准点,求出s[i]表示i号点到1号点的距离, sum
    总的环长。

    设我们找的两个环上节点是i,j
    max( min{s[i]-s[j],sum-(s[i]-s[j]} +d[i]+d[j])即为所求,

        取最大(取最小(i,j两点在环上的距离)+ 一个点到 i 的距离 + 一个点到 j 的距离)

    但如果暴力求是n^2的。并没有比最开始直接枚举快多少。
    考虑优化。

    我们考虑把内部的一个min去掉,式子能看起来更清晰一些。
    max( min{s[i]-s[j],sum-(s[i]-s[j])} +d[i]+d[j])
    Max( 1: s[i]-s[j] +d[i]+d[j] | s[j]>=s[i]-sum/2,
    2: sum-s[i]+s[j] +d[i]+d[j] | s[j]< s[i]-sum/2 )
    考虑枚举选的两个点的后一个点i,然后求对于i,离i最远的j距离是多少,然后
    对于每一个i的答案求最大值就是整个基环树的直径了。
    第一种情况s[j]>=s[i]-sum/2 :求d[j]-s[j]的最大值即可,注意可选的j区间会移动,
    所以这里需要单调队列。
    第二种情况s[j]< s[i]-sum/2:这个可行区间只会变大,不会缩小,所以直接记录
    s[j]+d[j]的最大值即可。

    总结

  • 相关阅读:
    python 正则表达式练习题
    python2与Python3的区别
    Python :生成一个1到50的大字符串,每个数字之间有个空格 1 2 3 4 ..........50
    关于实现今天到一年中任意一天两者之间的天数差的计算
    Window下pip的安装
    Pycharm中Git、Github的简单使用和配置
    Python中字符串操作函数string.split('str1')和string.join(ls)
    实现无密码远程登陆另一台机器
    我的第一篇博客
    String类型转List<Integer>
  • 原文地址:https://www.cnblogs.com/xiaoyezi-wink/p/11314880.html
Copyright © 2011-2022 走看看