zoukankan      html  css  js  c++  java
  • 20201013 day34 复习8:动态规划之背包变式

    LIS&LCS

    最长上升子序列(LIS)

    POJ2533

    题意显然。
    子序列:序列({a_n}),则其一个子序列是存在一些数(j_x),使得(1le j_1<j_2<...le n),构成子序列({a_j})

    题解

    ( ext{O}(n^2))

    (f_i)表示以(a_i)结尾的上升子序列最长为多少。初始化显然每个(f都为1)。我们从(1)~(i-1)枚举(f_j),当(a_j<a_i)时,(f_i=max(f_i,f_j+1))
    最后我们输出最大的(f_i)即可。

    (O(nlog n))

    设原数组为(a_n),设(f_i)表示长度为(i)的序列中最长上升的最小值(他是原数组的数值)

    如果a[i]>dp[len],len表示当前LIS最长长度,就直接放入末尾
    否则,在dp数组中寻找第一个大于等于a[i]的位置,把他替换成a[i],这样的话dp[len]的数值变得稍小,可以更好地在后面接上升序列

    for(int i=1; i<=n; i++){
            if(a[i]>dp[len]) dp[++len]=a[i];
            else{
                dp[lower_bound(dp+1,dp+1+len,a[i])-dp]=a[i];
            }
        }
    

    最长公共子序列(LCS)

    01背包(一些优化)

    空间复杂度优化

    二维的01背包很好写。时间复杂度和空间复杂度均为(O(NV)),而空间复杂度则可以优化到(O(V))
    我们考虑二维数组(F(i,0...V))优化到(F(0...V)),我们一定会需要一个主循环i-->N,而我们考虑,(F(i,v))是由(F(i-1,v))(F(i-1,v-C_i))两个子问题递推而来,能否保证在推(F(i,v))时(也就是在第(i)次主循环时)能够取用(F(i-1,v),F(i-1,v-C_i))的值?事实上,这要求在每次主循环中我们以(v=V...0)的递减顺序计算(F(v)),这样才能保证(F(v))(F(v-C_i))保存的是状态(F(i-1,v-C_i))的值。

    for i=1 to N
          for v=V to C[i]
                F[v]=max{F[v],F[v-C[i]]+W[i]};
    

    其中的最后一句,对应原来的转移方程,因为现在的(F(v-C_i))就相当于原来的(F(i-1,v-C_i)),如果改成顺序,就变成由(F(i,v))(F(i,v-C_i))推导得到。

    恰好装满

    仅仅是在初始化的时候,普通的背包都初始化为0,而恰好装满要初始化为(-∞).

    常数优化

    第二重循环的下限可以改进。它可以被优化为:
    for(int (v=V) to (max(sumlimits_i^NW_i,C_i))

    完全背包(一些优化)

    一个简单有效的优化

    根据性价比去掉某些物品。总的来说,(C_ile C_j,W_ige W_j),此时就可以去掉(j)物品。这个优化可以(O(N^2))解决。
    另一种优化是:把(V_ige V)的物品山区,然后利用类似计数排序的原理,计算出相同费用中价值最高的是哪个,可以(O(V+N))完成优化。

    转化成01背包

    考虑到第(i)种物品最多选(lfloordfrac{V}{C_i} floor),于是可以把第(i)种物品转化为(lfloordfrac{V}{C_i} floor)件费用和价值均不变的01背包。这个做法丝毫没有改进时间复杂度,但是指明了转化的思路。
    另一种更高效的转化方法是:把第(i)种物品拆分成费用为(C_i2^k),价值为(W_i2^k)的若干件物品,其中(kinleft[0,log_2left( dfrac{V}{C_i} ight) ight]),即(C_i2^kle V)的非负整数(k)
    这是二进制的思想,因为不管最优策略选几件第(i)种物品,其件数携程二进制后,总能表示称若干个(2^k)件物品的和,这样就把每种物品拆成(Oleft(logdfrac{V}{C_i} ight)),是一个很大的改进。

    (O(VN))的算法

          F[0..V ] = 0
          for i = 1 to N
                for v = Ci to V
                      F[v] = max(F[v], F[v − Ci] + Wi)
    

    你会发现,这个伪代码与01背包问题的伪代码只有v的循环次序不同而已。
    为什么这个算法就可行呢?首先想想为什么01背包中要按照v递减的次序来循环。让v递减是为了保证第i次循环中的状态(F[i, v])是由状态(F[i − 1, v − C_i])递推而来。换句话说,这正是为了保证每件物品只选一次,保证在考虑“选入第i件物品”这件策略时,依据的是一个绝无已经选入第i件物品的子结果(F[i −1, v − C_i])。而现在完全背包的特点恰是每种物品可选无限件,所以在考虑“加选一件第i种物品”这种策略时,却正需要一个可能已选入第i种物品的子结果(F[i, v − C_i]),所以就可以并且必须采用v递增的顺序循环。这就是这个简单的程序为何成立的道理。
    值得一提的是,上面的伪代码中两层for循环的次序可以颠倒。这个结论有可能会带来算法时间常数上的优化。
    这个算法也可以由另外的思路得出。例如,将基本思路中求解(F[i, v − C_i])的状态转移方程显式地写出来,代入原方程中,会发现该方程可以等价地变形成这种形式:(F[i, v] = max(F[i − 1, v], F[i, v − C_i] + W_i))
    将这个方程用一维数组实现,便得到了上面的伪代码。
    最后抽象出处理一件完全背包类物品的过程伪代码:

    def CompletePack(F, C, W)
          for v = C to V
                F[v] = max{F[v], f[v − C] + W}
    

    多重背包

    分组背包

    有依赖的背包

    二维费用背包

    泛化物品背包

    其他变式

  • 相关阅读:
    希尔排序(java实现)
    直接插入排序(java实现)
    android AsyncTask使用限制
    android TranslateAnimation动画执行时的坐标获取。
    android内存管理机制
    android实现前置后置摄像头相互切换
    【转-整理】JavaWeb框架中,各层的解释和关系
    安卓系统上安装.net运行时 mono runtime
    你不知道的https工作原理
    HTTPS的误解(二)
  • 原文地址:https://www.cnblogs.com/liuziwen0224/p/20201013day34-002.html
Copyright © 2011-2022 走看看