动态规划之线性DP详解

发布于:2022-12-25 ⋅ 阅读:(508) ⋅ 点赞:(0)


什么是线性DP?

线性dp,所谓线性dp,就是指我们的递归方程有一个明显的线性关系的,有可能是一维线性的,也可能是二维线性的.

典型例题一:大盗阿福

1.1 原题

阿福是一名经验丰富的大盗。趁着月黑风高,阿福打算今晚洗劫一条街上的店铺。
这条街上一共有 NN 家店铺,每家店中都有一些现金。阿福事先调查得知,只有当他同时洗劫了两家相邻的店铺时,街上的报警系统才会启动,然后警察就会蜂拥而至。
作为一向谨慎作案的大盗,阿福不愿意冒着被警察追捕的风险行窃。他想知道,在不惊动警察的情况下,他今晚最多可以得到多少现金?
输入格式
输入的第一行是一个整数 T (T≤50) ,表示一共有 TT 组数据。
接下来的每组数据,第一行是一个整数 N (1≤N≤100000),表示一共有 N 家店铺。
第二行是 N 个被空格分开的正整数,表示每一家店铺中的现金数量。每家店铺中的现金数量均不超过 1000。
输出格式
对于每组数据,输出一行。
该行包含一个整数,表示阿福在不惊动警察的情况下可以得到的现金数量。
提示
对于第一组样例,阿福选择第 2家店铺行窃,获得的现金数量为 8。对于第二组样例,阿福选择第 1 和 4 家店铺行窃,获得的现金数量为 10 + 14 = 24。
样例输入
2
3
1 8 2
4
10 7 6 14
样例输出
8
24

1.2 分析方法一

1.2.1 状态表示:
f[i] 表示偷前i家店铺能获取的最大值。
1.2.2 状态转移:
状态转移可以用带权的有向图表示,点表示状态,权表示转移的价值增量。
在这里插入图片描述
转移的情况:
1、不选第 i 家店铺,那么就可以选择第 i-1家店铺,所以f[i]=f[i-1]
2、选第 i 家店铺,那么就不能选择第 i-1 家店铺,只能选择第 i-2 家店铺,因为会选择第 i 家店铺,所以f[i]=f[i-2]+w[i]
1.2.3 边界情况:
根据题意即可知边界为:

  1. 第0家店铺:f[0]=0
  2. 第1家店铺:f[1]=w[1]

1.2.4 完整代码:

#include<bits/stdc++.h>
using namespace std;
const int N=500010;
int f[N];
int w[N];
int main ()
{
    int n,m;
    cin>>n;
    for(int i=1;i<=n;i++){
        cin>>m;
        for(int i=1;i<=m;i++)
        cin>>w[i];
        //dp
        f[1]=w[1];//边界条件
        for(int i=2;i<=m;i++)
            f[i]=max(f[i-1],f[i-2]+w[i]);

        cout<<f[m]<<endl;
    }
    return 0;
}

1.3 分析方法二

1.3.1 状态表示:
f[i][0] 表示不偷第 i 家店铺能获得的最大值
f[i][1] 表示偷第 i 家店铺能获得的最大值

1.3.2 状态转移:
在这里插入图片描述
转移递推式:
不偷:f[i][0]=max( f[i-1][0] , f[i-1][1])
偷:f[i][1]=f[i-1][0] + w[i]
1.3.3 边界情况:
根据题意可知边界为:

  1. 不偷第 1 家店铺:f[1][0]=0
  2. 偷第 1 家店铺 f[i][1]=w[1]

1.4 完整代码:

//  时间:2022.09.07 18点09分
//  算法:线性DP
#include<bits/stdc++.h>
using namespace std;
const int N=510;
int f[N][N];
int w[N];
int main ()
{
    int n,m;
    cin>>n;
    for(int i=1;i<=n;i++){
        cin>>m;
        for(int i=1;i<=m;i++)
            cin>>w[i];
        //dp
        f[1][0]=0,f[1][1]=w[1];
        for(int i=2;i<=m;i++){
            f[i][0]=max(f[i-1][0],f[i-1][1]);
            f[i][1]=f[i-1][0]+w[i];
        }
        cout<<max(f[m][0],f[m][1])<<endl;
    }
    return 0;
}

典型例题二:股票买卖

2.1 原题:

给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。
设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)。
注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。

2.2 分析:

2.2.1 状态表示:

  1. f[i][0] 表示第 i 天手中无票时获取的最大利润
  2. f[i][1] 表示第 i 天手中有票时获得最大利润

2.2.2 状态转移:
在这里插入图片描述
状态转移递推式:

  1. 无票:f[i][0]=max(f[i-1][0],f[i-1][1] + w[i])
  2. 有票:f[i][1]=max(f[i-1][1], f[i-1][0] - w[i])

2.2.3 边界情况:

  1. 第1天无票:f[1][0]=0
  2. 第1天有票:f[1][1]=w[1]

2.3 完整代码:

//  时间:2022.09.07 19点56分
//  算法:线性DP
#include<bits/stdc++.h>
using namespace std;
const int N=510;
int f[N][N];
int w[N];
int main ()
{
    int n;
    cin>>n;
    for(int i=1;i<=n;i++)
        cin>>w[i];
        
        f[1][0]=0,f[1][1]= -w[1];
        //dp
        for(int i=2;i<=n;i++){
            f[i][0]=max(f[i-1][0],f[i-1][1]+w[i]);
            f[i][1]=max(f[i-1][1],f[i-1][0]-w[i]);
        }

        cout<<f[n][0]<<endl;
    return 0;
}

典型例题三:股票买卖K笔交易

3.1 原题:

题目链接

3.2 分析:

3.2.1 状态表示:

  1. f(i,j,0) :表示前 i 天买卖了 j 次,当前手中无票,能获取的最大利润。
  2. f(i,j,1):表示前 i 天买卖了 j 次,当前手中有票,能获取的最大利润。

3.2.2 状态转移:
带权的有向图,点表示状态,边权表示转移的价值增量。
在这里插入图片描述
状态递推式:

  1. 无票:f(i,j,0)=max(f(i-1,j,0) , f(i-1,j,1) + w[i])
  2. 有票:f(i.j.1)=max(f(i-1,j,1) , f(i-1,j-1,0) - w[i])

3.2.3 边界情况:
(i:1 ~ n; j: 1 ~ k)
第0天:f(0,j,0)=0
f(0,j,1)=-1e6
第0笔:f(i,0,0)=0

边界赋值技巧:
合法则赋可取的有限值
非法则赋负无穷或正无穷

3.3 完整代码:

#include<bits/stdc++.h>
using namespace std;

const int N=100;
int f[N][N][N];
int w[110];

int main ()
{
    int n,k;
    cin>>n>>k;
    for(int i=1;i<=n;i++) cin>>w[i];
    //边界情况
    for(int i=0;i<=k;i++) f[0][i][1]=-1e6;
    
    for(int i=1;i<=n;i++)   
        for(int j=1;j<=k;j++){
            f[i][j][0]=max(f[i-1][j][0],f[i-1][j][1]+w[i]);
            f[i][j][1]=max(f[i-1][j][1],f[i-1][j-1][0]-w[i]);
        }
        cout<<f[n][k][0]<<endl;
    return 0;
}

典型例题四:数字三角形

4.1 题目:

题目链接

4.2 分析:

在这里插入图片描述

4.3 完整代码:

// 算法:动态规划之线性DP
// 时间:2022.07.13 15点45分
#include<iostream>
#include<algorithm>
using namespace std;

const int N=510;
int n,INF=1e9;
int a[N][N],f[N][N];
int main ()
{
    cin>>n;
    for(int i=1;i<=n;i++)
        for(int j=1;j<=i;j++)
            cin>>a[i][j];

    for(int i=0;i<=n;i++)
        for(int j=0;j<=i+1;j++)
//注意此时初始化需要多初始化一位,因为算边界时会算右上角的最大值,所以把最上角初始化为负无穷    
            f[i][j]=-INF;
    f[1][1]=a[1][1];
    for(int i=2;i<=n;i++)
        for(int j=1;j<=i;j++)
            f[i][j]=max(f[i-1][j-1]+a[i][j],f[i-1][j]+a[i][j]);
        
    //遍历最后一行的最大值
    int res=-INF;
    for(int i=1;i<=n;i++) res=max(res,f[n][i]);
    
    cout<<res<<endl;
    return 0;
}

本文含有隐藏内容,请 开通VIP 后查看