0x05 动态规划

动态规划

Dynamic Programming

背包问题

graph LR A(背包问题)-->B(01背包)-->F(每件物品只能选0或1次) A-->C(完全背包)-->G(每件物品可以无限次选取) A-->D(多重背包)-->H(每个物品最多选有限次) A-->E(分组背包)-->I(每个组中只能选一件物品)

思路

graph LR A(DP)-->B(状态表示)-->D(集合) B-->E(属性:最大最小、数量) D-->F(所有选法) D-->G(条件)-->H(怎样选) G-->I(总体积) A-->C(状态计算)-->J(集合划分)

01背包

每件物品只能选0或1次

给定容量为 \(W\) 的背包和 \(n\) 件物品,每件物品都有重量 \(w_i\) 和价值 \(v_i\) ,每件物品只能选取 0 次或 1 次。求背包内物品的最大化价值

状态转移方程:

\(dp[i][j]=\max (dp[i-1][j],dp[i-1][j-w_i]+v_i)\)

\(dp[i][j]\):从前 \(i\) 个物品中选择并放入容量为 \(j\) 的背包时,可以获得的最大价值

\(dp[i-1][j]\):不选第 \(i\) 个物品

\(dp[i-1][j-w_i]+v_i\):选择第 \(i\) 个物品

滚动数组优化:

\(dp[j]=\max (dp[j-1],dp[j-v[i]]+w[i])\)

//二维
for(int i=1;i<=n;i++)
    for(int j=1;j<=m;j++)
    {
        dp[i][j]=dp[i-1][j];
        if(j>=v[i]) dp[i][j]=max(dp[i-1][j],dp[i-1][j-v[i]]+w[i]);
    }
//一维
for(int i=1;i<=n;i++)
    for(int j=m;j>=v[i];j--)//从后向前遍历避免数据覆盖
        dp[j]=max(dp[j],dp[j-v[i]]+w[i]);

完全背包

每件物品可以无限次选取

状态转移方程:

\(dp[i][j]=\max (dp[i-1][j-k \cdot v[i]]+k \cdot w[i])\)

优化:

\[\begin{array}{rl} dp[i,j] &=&\max (dp[i-1,j],dp[i-1,j-v]+w,dp[i-1,j-2v]+2w,\dots,dp[i-1,j-kv]+kw) \\ dp[i,j-v] &=&\max (dp[i-1,j-v],dp[i-1,j-2v]+w,\dots,dp[i-1,j-kv]+(k-1)w) \\ \Longrightarrow &&dp[i,j]=\max (f[i-1,j],f[i][j-v]+w) \\ \end{array}\]

//二维
for(int i=1;i<=n;i++)
    for(int j=1;j<=m;j++)
    {
        dp[i][j]=dp[i-1][j];
        if(j>=v[i]) dp[i][j]=max(dp[i][j],dp[i][j-v[i]]+w[i]);
    }
//一维
for(int i=1;i<=n;i++)
    for(int j=v[i];j<=m;j++)
        dp[j]=max(dp[j],dp[j-v[i]]+w[i]);//从前向后遍历,此时dp[j-v[i]]为第 i 层

多重背包

每个物品最多选有限次

状态转移方程:

\(dp[i][j]=\max (dp[i-1][j-k\cdot v[i]]+k\cdot w[i])\quad(k=0,1,2,\dots,s[i])\)

优化:

\(S\)用二进制表示

\(O(n^3)\rightarrow O(n^2\log S)\)

//二维
for(int i=1;i<=n;i++)
    for(int j=1;j<=m;j++)
    	for(int k=0;k<=s[i]&&k*v[i]<=j;k++)
            dp[i][j]=max(dp[i][j],dp[i-1][j-v[i]*k]+w[i]*k);
//一维
for(int i=1;i<=n;i++)
    for(int j=m;j>=0;j--)
    	for(int k=0;k<=s[i]&&k*v[i]<=j;k++)
            dp[j]=max(dp[j],dp[j-v[i]*k]+w[i]*k);
//优化
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;
        k*=2;
    }
    if(s>0)
    {
        cnt++;
        v[cnt]=a*s;
        w[cnt]=b*s;
    }
}
n=cnt;
for(int i=1;i<=n;i++)//转化为 01 背包问题
    for(int j=m;j>=v[i];j--)
        dp[j]=max(dp[j],dp[j-v[i]]+w[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)
                dp[j]=max(dp[j],dp[j-v[i][k]]+w[i][k]);

线性DP

最长上升子序列(LCA)

求最长的元素单调递增的子序列的长度

最长公共子序列(LCS)

区间DP

posted @ 2026-01-06 20:38  YamadaRyou  阅读(12)  评论(0)    收藏  举报