【转】关于LIS和一类可以用树状数组优化的DP 预备知识

原文链接 http://www.cnblogs.com/liu-runda/p/6193690.html

  1. 预备知识

    DP(Dynamic Programming):一种以无后效性的状态转移为基础的算法,我们可以将其不严谨地先理解为递推。例如斐波那契数列的递推求法可以不严谨地认为是DP。当然DP的状态也可以是二维/三维的,某一维的含义也不仅仅是指某个数列的第几项。

    树状数组(BIT or fenwick tree):一种高效地动态维护一个序列并动态求取前缀和的数据结构。修改某个元素/求一次前缀和的时间复杂度均为O(logn)

  2 LIS

    好了现在假设你们都会打树状数组了(如果不会的话,baidu一发吧!顺便把线段树也自学了吧!如果学不会树状数组只把线段树自学了也成因为好像并没有树状数组能干而线段树干不了的事情),让我们做一道例题练习一下吧!。

    给出一个长度为n的序列,求LIS长度(LIS,longest increasing sequence,最长上升子序列,子序列定义为从原序列取出的一些未必相邻但保持原先的相对顺序的数,上升子序列满足子序列中前一个数严格小于后面的数)

    50% n<=5000,100% n<=50000

    50%的数据其实并没有用到树状数组。

    O(n^2)的LIS算法:定义f[i]为强制以第i项结尾时的LIS长度。那么从1到n依次求出每个f[i],f[i]可以由f[1...i-1]推出。

    好了,现在假设你们都会写50分了。我们考虑把算法优化到能跑50000

    首先离散化,将原先的数字对应到1-tot(tot为出现过的数值种类),例如10,10,20,15变成1,1,3,2

    那么我们定义g[i]为以数值i结尾的LIS长度,并从左向右扫描原数组,同时维护g数组。扫描1,1,3,2时,g数组变化如下:(未出现的项的值为0)

      a.g[1]=1

      b.g[1]=1

      c.g[1]=1,g[3]=2

      d.g[1]=1,g[2]=2,g[3]=2

    YY一下我们怎样用g数组完成DP。

    接下来我们需要一种数据结构支持单点修改(只增不减)和前缀最大值查询。

    首先来一个逗比分块写法维护g数组:将g均分成sz块,维护每一块的最大值。修改可以O(1)完成,前缀最大值查询可以将整块整块的部分用每一块的最大值求,零散部分暴力扫一遍。sz取在sqrt(n)附近的时候效率最高。总时间复杂度O(nsqrt(n)),自信AC。

    如果n<=233333?

    我们可以树状数组维护g数组。原始的树状数组改造后可以支持单点修改(只增不减)和前缀最大值查询。把树状数组的加法换成取最大值,YY一下。

    最后整个g数组的最大值就是答案。

  3. LCS

    好了,现在假设你们都会nlogn的LIS,让我们来一道例题练习一下吧!

    uva10635 Prince and Princess(数据范围是我口胡的)

    给出两个1~n的排列(一个1~n的排列中,1-n每个数字出现一次,不重复出现),求LCS(最长公共子序列)长度。50%的数据保证第一个排列为1,2,3,...n按顺序递增的。

    n<=233333

    50%数据是第二个排列的LIS。很好50分到手。

    下面设想一下,把数字重新编号,LCS长度不变。例如,两个序列为1,2,3; 3,2,1.那么a,b,c;c,b,a的答案是和它一样的。那么把数字编号为数字也是可以的。

    例如这两个数据:

    (1)

    4

    1 2 3 4

    2 3 4 1

    (2)

    4

    4 3 2 1

    3 2 1 4

    它们的答案是相同的。因为在求解LCS时,我们只关注两个序列某两个下标处的数值是否相同,重新编号没有改变“对应下标位置的数相同”的信息。

    那么我们把一个序列重编号为1,2,3...n,对另一个序列也用同样的对应关系重编号(即把第二个序列中每个数改成这个数在第一个序列中出现的下标),跑LIS,自信AC。

  4. more about LCS

    接下来我们分兵奇袭,用另一条思路得出刚才例题lcs的另一个nlogn做法。

    首先自己yy一下LCS的O(n^2)算法。它的状态定义为f[i][j]表示允许使用第一个序列的前i个元素,第二个序列的前j个元素(其中第i个,第j个元素不要求必须使用)

    接下来我们修改状态定义(这种事情在优化DP时需要经常干)使得它能优化到nlogn.

    新的状态中,f[i][j]表示允许使用第一个序列的前i个元素,第二个序列的前j个元素(其中第一个序列第i个元素不必须使用,第二个序列第j个元素必须使用

    于是我们把原先的DP改成分层的过程,考虑从f[i-1][]求出f[i][],那么这两个数组最多有一个位置不同(只有以第一个序列的第i个元素结尾的答案会发生改变)。

    那么我们动态维护这个数组就可以了。假如第一个序列中第i个元素在第二个序列中出现在第k个位置,那么修改f[k]即可(这里已经干掉了第一维)。

    f[k]等于什么?等于f[1...k-1]的最大值+1,因为在LCS中前一项一定是第二个序列前面k-1的项中的一项(如果已经是第一项,没有前一项?边界自己处理一下我就不(懒得)写了)。

    于是还是树状数组前缀最大值,只增不减的单点修改。

    事实上,这个算法只要保证匹配上的位置不多即可。因此1-n每个数字出现5次也是能写的。

    http://www.lydsy.com/JudgeOnline/problem.php?id=1264 bzoj1264基因匹配

  5。more about BIT&DP

    二维树状数组也是可以拿来优化DP的 http://www.lydsy.com/JudgeOnline/problem.php?id=3594  方伯伯的玉米田

    坐标系可以转...http://www.lydsy.com/JudgeOnline/problem.php?id=2131  免费馅饼

    和树状数组没关系的傻逼题 http://www.lydsy.com/JudgeOnline/problem.php?id=4300

    2016.12.31update:终于想到了一道noip题NOIP2012摆花 http://cogs.pro/cogs/problem/problem.php?pid=1270

  6. 巩固练习一下LIS

    http://www.lydsy.com/JudgeOnline/problem.php?id=3173

    http://www.lydsy.com/JudgeOnline/problem.php?id=4660

由于我太弱,所以这里的例题比较少...虚心求dalao们在评论区提供例题。

上一篇:容器在 Weave 中如何通信和隔离?- 每天5分钟玩转 Docker 容器技术(65)


下一篇:Some About Spring