其他分享
首页 > 其他分享> > 开通博客园

开通博客园

作者:互联网

注意
1.0 学习

#include <iostream>
int main()
{
  return 0;
}

测试

动态规划

包括线性DP(数字三角形模型、最长上升子序列模型、编辑距离)、背包模型(01背包、完全背包、多重背包、分组背包、混合背包...)、状态机、状态压缩DP、区间DP(石子合并..)、树形DP、数位DP、单调队列优化DP、斜率优化DP等内容

动态规划

传统dp:
关键词:状态表示、状态转移、最优子结构、重叠子问题、后效性
DP问题一般是求若干有限集合中的最值问题,并且此类问题存在子问题重叠(Overlap-subproblems),如果通过暴力枚举每种可能解决问题,那么将会有大量重复的计算;如果从集合角度来分析DP问题,每次枚举一个集合并且利用子问题重叠特性,将子问题的解存储下来,可以大大提高效率;
DP问题的核心是状态集合 f(i) 的定义,在状态及转移计算中,划分子集的依据:寻找最后一个不同点,即处理最后一个子集的选择。

闫氏DP分析法
从集合角度考虑DP问题,又称闫氏分析法,包含以下两步:

数字三角形模型

此类问题中:

最长上升子序列模型

(LIS)Longest Increasing Subsequence
此类问题中:

背包模型

Article:dd大牛的背包九讲-背包问题汇总

背包模型一般求解

  1. 最大值、最小值
  2. 方案数
  3. 具体方案: 最短路问题,即记录转移过程,不能再用状态压缩了?而且如果输出要求字典序最小的方案,则需要对i的遍历顺序变化!!??

初始化汇总
背包问题中 体积至多是 j ,恰好是 j ,至少是 j 的初始化问题的研究

学背包问题的过程
1、一开始学背包问题时遇到的大多数的状态表示是:从前i个物品中选,且总体积不超过j的问题。
2、慢慢地在提高课中,就有出现状态表示是:从前i个物品中选,且总体积恰好是j的问题。例如 AcWing 1023. 买书 ,求的是恰好是j的总方案数问题。
3、同时还出现了状态表示是:从前i个物品中选,且总体积至少是j的问题。例如 AcWing 1020. 潜水员 ,求的是总体积至少是j的最小价值
可以观察到,他们的分析方法以及状态转移方程都是一样的,唯独是初始化有很大的不同

最大值最小值初始化总结
- 二维情况
1、体积至多j,f[i,k] = 0,0 <= i <= n, 0 <= k <= m(只会求价值的最大值)
2、体积恰好j,
当求价值的最小值:f[0][0] = 0, 其余是INF
当求价值的最大值:f[0][0] = 0, 其余是-INF
3、体积至少j,f[0][0] = 0,其余是INF(只会求价值的最小值)

- 一维情况
1、体积至多j,f[i] = 0, 0 <= i <= m(只会求价值的最大值)
2、体积恰好j,
当求价值的最小值:f[0] = 0, 其余是INF
当求价值的最大值:f[0] = 0, 其余是-INF
3、体积至少j,f[0] = 0,其余是INF(只会求价值的最小值)
for 物品 i  
    for 体积 j  
        for 决策 k或s   

1.01背包问题

有N件物品和一个容量为V的背包。第i件物品的费用是v[i],价值是w[i]。求解将哪些物品装入背包可使价值总和最大。
f[i][j]=max{f[i-1][j],f[i-1][j-v[i]]+w[i]}

“将前i件物品放入容量为v的背包中”这个子问题,若只考虑第i件物品的策略(放或不放),那么就可以转化为一个只牵扯前i-1件物品的问题。如果不放第i件物品,那么问题就转化为“前i-1件物品放入容量为v的背包中”,价值为f[i-1][j];如果放第i件物品,那么问题就转化为“前i-1件物品放入剩下的容量为j-v[i]的背包中”,此时能获得的最大价值就是f[i-1][j-v[i]]再加上通过放入第i件物品获得的价值w[i]。

优化
以上时间和空间复杂度均为O(N*V)
可把空间优化为O(V)

for i=1..N
    for j=V..0     // !!!
        f[j]=max{f[j],f[j-v[i]]+w[i]};

说明:
其中的f[v]=max{f[v],f[v-c[i]]}一句恰就相当于我们的转移方程f[i][v]=max{f[i-1][v],f[i-1][v-c[i]]},因为现在的f[v-c[i]]就相当于原来的f[i-1][v-c[i]]。如果将v的循环顺序从上面的逆序改成顺序的话,那么则成了f[i][v]由f[i][v-c[i]]推知,与本题意不符,但它却是另一个重要的背包问题P02最简捷的解决方案,故学习只用一维数组解01背包问题是十分必要的。

关于初始化

2. 完全背包问题

有N种物品和一个容量为V的背包,每种物品都有无限件可用。第i种物品的费用是c[i],价值是w[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。

通过


其中s为体积限制下该物品的最大数量。
即优化过程为:

f[i , j ] = max( f[i-1,j] , f[i-1,j-v]+w ,  f[i-1,j-2*v]+2*w , f[i-1,j-3*v]+3*w , .....)
f[i , j-v]= max(            f[i-1,j-v]   ,  f[i-1,j-2*v] + w , f[i-1,j-3*v]+2*w , .....)
由上两式,可得出如下递推关系: 
    f[i][j]=max(f[i,j-v]+w , f[i-1][j]) 

f[i][j]=max{f[i-1][j-k*v[i]]+k*w[i] | 0<=k*c[i]<=v}

for(int i = 1 ; i<=n ;i++)
    for(int j = 0 ; j<=m ;j++)
    {
        for(int k = 0 ; k*v[i]<=j ; k++)
            f[i][j] = max(f[i][j],f[i-1][j-k*v[i]]+k*w[i]);
    }
cout<<f[n][m]<<endl;

可优化为
f[i][j]=max{f[i-1][j], f[i-1][j-v[i]]+w[i]}
具体代码为:

for(int i = 1 ; i <=n ;i++)
    for(int j = 0 ; j <=m ;j++)
    {
        f[i][j] = f[i-1][j];
        if(j-v[i]>=0)
        // 注意第二部分 :  f[i][j-v[i]]
            f[i][j]=max(f[i][j],f[i][j-v[i]]+w[i]);   
    }
cout<<f[n][m]<<endl;

滚动数组优化后代码为
f[j] = max(f[j], f[j-v[i]] + w[i]);
具体代码为:

 for(int i = 1 ; i<=n ;i++)
    for(int j = v[i] ; j<=m ;j++)//注意了,这里的j是从小到大枚举,和01背包不一样
    {
            f[j] = max(f[j],f[j-v[i]]+w[i]);
    }

对比01背包:
f[i][j]=max{f[i-1][j], f[i-1][j-v[i]]+w[i]}
具体代码为:

for(int i = 1 ; i <= n ; i++)
    for(int j = 0 ; j <= m ; j ++)
    {
        f[i][j] = f[i-1][j];
        if(j-v[i]>=0)
        // 注意第二部分 :  f[i-1][j-v[i]]
            f[i][j] = max(f[i][j],f[i-1][j-v[i]]+w[i]);  
    }

优化后为

综上:
滚动优化前 两者相差一点:
f[i][j] = max(f[i][j],f[i-1][j-v[i]]+w[i]);//01背包
f[i][j] = max(f[i][j],f[i][j-v[i]]+w[i]);//完全背包问题
滚动优化后 两者代码相同:
f[j] = max(f[j],f[j-v[i]]+w[i]);//01背包 倒序啊!

f[j] = max(f[j],f[j-v[i]]+w[i]);//完全背包问题

分组倍增优化
完全背包也能进行分组倍增优化,每种物品的最大数量为j/v[i],但优化后复杂度不如单调队列优化,所有暂不用该优化。此优化可用于多重背包问题中。见下文。

3. 多重背包问题

方法:

  1. 暴力 转化成01背包
  2. 分组倍增优化 转化成01背包
  3. 最大长度的队列优化,即滑动窗口最大值,模型为单调队列(难)

分组倍增优化(二进制优化)
对于每种物品,均执行:
对于i种物品,最多用s个。此时把s拆分成logs组,1,2,4,...2^logs,每组选或不选,最终能凑成选择0~s个的任意值。将原始物品删除,用新的物品代替其体积和价值。转化为01背包。

将第i种物品分成若干件物品,其中每件物品有一个系数,这件物品的费用和价值均是原来的费用和价值乘以这个系数。使这些系数分别为1,2,4,...,2(k-1),n[i]-2k+1,且k是满足n[i]-2^k+1>0的最大整数。例如,如果n[i]为13,就将这种物品分成系数分别为1,2,4,6的四件物品。
分成的这几件物品的系数和为n[i],表明不可能取多于n[i]件的第i种物品。另外这种方法也能保证对于0..n[i]间的每一个整数,均可以用若干个系数的和表示,这个证明可以分0..2k-1和2k..n[i]两段来分别讨论得出.
这样就将第i种物品分成了O(log n[i])种物品,将原问题转化为了复杂度为O(V*Σlog n[i])的01背包问题.

未优化代码:

for (int i = 1; i <= n; i ++ )
    for (int j = 0; j <= m; j ++ )
        for (int k = 0; k <= s[i] && k * v[i] <= j; k ++ )
            f[i][j] = max(f[i][j], f[i - 1][j - v[i] * k] + w[i] * k);

优化过程:

在完全背包中,通过两个状态转移方程:

f[i , j ] = max( f[i-1,j] , f[i-1,j-v]+w , f[i-1,j-2v]+2w , f[i-1,j-3v]+3w, .....)
f[i , j-v]= max( f[i-1,j-v] , f[i-1,j-2v] + w, f[i-1,j-2v]+2w , .....)

通过上述比较,可以得到 f[ i ][ j ] = max(f[ i - 1 ][ j ],f[ i ][ j - v ] + w)。

再来看下多重背包,

f[i , j ] = max( f[i-1,j] ,f[i-1,j-v]+w ,f[i-1,j-2v]+2w ,..... f[i-1,j-Sv]+Sw, )
f[i , j-v]= max( f[i-1,j-v] ,f[i-1,j-2v]+w, ..... f[i-1,j-Sv]+(S-1)w, f[i-1,j-(S+1)v]+Sw )

二进制优化,它为什么正确,为什么合理,凭什么可以这样分??

优化后代码:

    int cnt = 0; //分组的组别
    for(int i = 1;i <= n;i ++)
    {
        int a,b,s;
        cin >> a >> b >> s;
        int k = 1; // 组别里面的个数
        while(k<=s)
        {
            cnt ++ ; //组别先增加
            v[cnt] = a * k ; //整体体积
            w[cnt] = b * k; // 整体价值
            s -= k; // s要减小
            k *= 2; // 组别里的个数增加
        }
        //剩余的一组
        if(s>0)
        {
            cnt ++ ;
            v[cnt] = a*s; 
            w[cnt] = b*s;
        }
    }

    n = cnt ; //枚举次数正式由个数变成组别数

    //01背包一维优化
    for(int i = 1;i <= n ;i ++)
        for(int j = m ;j >= v[i];j --)
            f[j] = max(f[j],f[j-v[i]] + w[i]);

解析:

(1)我们知道转化成01背包的基本思路就是:判断每件物品我是取了你好呢还是不取你好。

(2)我们知道任意一个实数可以由二进制数来表示,也就是20~2k其中一项或几项的和。

(3)这里多重背包问的就是每件物品取多少件可以获得最大价值。

分析:

如果直接遍历转化为01背包问题,是每次都拿一个来问,取了好还是不取好。那么根据数据范围,这样的时间复杂度是O(n^3),也就是1e+9,这样是毫无疑问是会TLE的。

假如10个取7个好,那么在实际的遍历过程中在第7个以后经过状态转移方程其实已经是选择“不取”好了。现在,用二进制思想将其分堆,分成k+1个分别有2k个的堆,然后拿这一堆一堆去问,我是取了好呢,还是不取好呢,经过dp选择之后,结果和拿一个一个来问的结果是完全一样的,因为dp选择的是最优结果,而根据第二点任意一个实数都可以用二进制来表示,如果最终选出来10个取7个是最优的在分堆的选择过程中分成了20=1,21=2,22=4,10 - 7 = 3 这四堆,然后去问四次,也就是拿去走dp状态转移方程,走的结果是第一堆1个,取了比不取好,第二堆2个,取了比不取好,第三堆四个,取了比不取好,第四堆8个,取了还不如不取,最后依旧是取了1+2+4=7个。

Tip:参考博客

如果仍然不是很能理解的话,取这样一个例子:要求在一堆苹果选出n个苹果。我们传统的思维是一个一个地去选,选够n个苹果就停止。这样选择的次数就是n次

二进制优化思维就是:现在给出一堆苹果和10个箱子,选出n个苹果。将这一堆苹果分别按照1,2,4,8,16,.....512分到10个箱子里,那么由于任何一个数字x ∈[1,1024]
都可以从这10个箱子里的苹果数量表示出来,但是这样选择的次数就是 ≤10次 。

这样利用二进制优化,时间复杂度就从O(n3)降到O(n2logS),从4*109降到了2*107。

单调队列优化

多重背包III

关键词: 单调队列、斜率优化、

for (int i = 0; i < n; i ++ )
    {
        int v, w, s;
        cin >> v >> w >> s;
        memcpy(g, f, sizeof f);
        for (int j = 0; j < v; j ++ )
        {
            int hh = 0, tt = -1;
            for (int k = j; k <= m; k += v)
            {
                if (hh <= tt && q[hh] < k - s * v) hh ++ ;
                while (hh <= tt && g[q[tt]] - (q[tt] - j) / v * w <= g[k] - (k - j) / v * w) tt -- ;
                q[ ++ tt] = k;
                f[k] = g[q[hh]] + (k - q[hh]) / v * w;
            }
        }
    }

4. 二维费用的背包问题

空间优化后同一维费用,每个费用均采取倒序方式。

5. 分组背包

有 N 组物品和一个容量是 V 的背包。

每组物品有若干个,同一组内的物品最多只能选一个。
每件物品的体积是 vij,价值是 wij,其中 i 是组号,j 是组内编号。si为第i组的物品种类数量。

求解将哪些物品装入背包,可使物品总体积不超过背包容量,且总价值最大。

for (int i = 1; i <= n; i ++ )
    for (int j = m; j >= 0; j -- )
        for (int k = 0; k < s[i]; k ++ )
             if (v[i][k] <= j)
                f[j] = max(f[j], f[j - v[i][k]] + w[i][k]);

...

状态机

状态压缩

区间DP

树形DP

数位DP

单调队列优化DP

斜率优化DP

标签:背包,int,max,博客园,物品,开通,优化,DP
来源: https://www.cnblogs.com/afengcodes/p/15837189.html