动态规划笔记

动态规划专题


动态规划的递归写法和递推写法

递归写法

Fibonacci(斐波那契数列)

int F(int n)
{
    if(n == 0 || n == 1)    return 1;
    else return F(n-1) + F(n-2);
}
  • 缺点:重复计算,例如当n==5时,F(5) = F(4) + F(3),而F(4) = F(3) + F(2),多计算了一次F(3)。

  • 改进:用空间换时间,一维数组dp记录F(n)的结果,dp[i] = -1时表示未计算过

int dp[MAXN];

int F(int n)
{
    if(n == 0 || n == 1)    return 1;
    if(dp[n] != -1)     return dp[n];   //已经计算过结果返回
    else
    {
        dp[n] = F(n-1) + F(n-2);    
        return dp[n];                   //计算完结果返回
    }
}
  • 时间复杂度:O(2n)->O(n)
  • 空间复杂度:O(1)->O(n)

递推写法

数塔问题

  • 状态转移方程
dp[i][j] = max(dp[i+1][j], dp[i+1][j+1]) + f[i][j]
  • 状态:dp[i][j]
  • 决策:走位置[i][j]的左下还是右下
  • 边界:可以直接确定其结果的部分

边界dp出在最底层各位置,不断往上求出每一层的dp值,最后会得到dp[1][1]

总结

  • 递推写法 自顶向上
  • 递归写法 自顶向下

一个问题必须拥有重叠子问题和最优子结构,才能使用动态规划去解决


最大连续子列和

  • 状态转移方程
d[i] = max(A[i], dp[i-1] + A[i])
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 10010;
int A[MAXN], dp[MAXN];
int main()
{
    int n;
    cin>>n;
    for(int i = 0; i < n; ++i)
        cin>>A[i];
    dp[0] = A[0];
    for(int i = 1; i < n; ++i)
        dp[i] = max(A[i], dp[i - 1] + A[i]);
    //dp[i]中存放以A[i]结尾的连续序列的最大和
    int k = 0;
    for(int i = 1; i < n; ++i)
        if(dp[i] > dp[k])
            k = i;
    cout<<dp[k];
    return 0;
}

状态的无后效性:当前状态记录了历史信息,一旦当前状态确定,就不会再改变,历史信息只能通过已有的状态去影响未来的决策

动态规划核心

  • 状态转移方程
  • 边界状态

最长不下降子序列

  • 状态转移方程
dp[i] = max(1, dp[j] + 1)
(j = 1,2,...,i-1 && A[j] < A[i])
for(int i = 1; i <= n; ++i)
{
    dp[i] = 1;  //边界初始条件,即每个元素自成一个序列
    for(int j = 1; j < i; ++j)
        if(A[i] > A[j] && dp[j] + 1 > dp[i])  //状态转移条件,即更新A[i]的条件
            dp[i] = dp[j] + 1;
    ans = max(ans, dp[i]);  //ans为最大的dp[i]
}
//1.A[i]>A[j],能够插入到以A[j]为末尾的队伍中
//2.dp[j] + 1,以A[j]为末尾的队伍的长度+1,即并入A[i]

最长公共子序列(子序列可以不连续)

  • 状态转移方程
    d p [ i ] [ j ] = { d p [ i − 1 ] [ j − 1 ] + 1 , A [ i ] = = B [ j ] m a x ( d p [ i − 1 ] [ j ] , d p [ i ] [ j − 1 ] ) , A [ i ] ! = B [ j ] dp[i][j]=\left\{ \begin{aligned} & dp[i - 1][j - 1] + 1, A[i] == B[j] \\ & max(dp[i - 1][j], dp[i][j - 1]), A[i] != B[j] \\ \end{aligned} \right. dp[i][j]={dp[i1][j1]+1,A[i]==B[j]max(dp[i1][j],dp[i][j1]),A[i]!=B[j]
  • 边界
    d p [ i ] [ j ] = 0 dp[i][j] = 0 dp[i][j]=0
//字符串从下标为1读入   
//边界全部设置为0,即无公共子序列,从dp[1][1]开始
for(int i = 1; i <= lenA; ++i)
{
    for(int j = 1; j <= lenB; ++j)
    {
        if(A[i] == B[i])
            dp[i][j] = dp[i - 1][j - 1] + 1;
        else
            dp[i][j] = max(dp[i][j-1], dp[i-1][j]);
    }
}
cout<<dp[lenA][lenB];

最长回文子串

  • 状态转移方程
    d p [ i ] [ j ] = { d p [ i + 1 ] [ j − 1 ] , S [ i ] = = S [ j ] 0 , S [ i ] ! = S [ j ] dp[i][j]=\left\{ \begin{aligned} & dp[i + 1][j - 1] , S[i] == S[j] \\ & 0, S[i] != S[j] \\ \end{aligned} \right. dp[i][j]={dp[i+1][j1],S[i]==S[j]0,S[i]!=S[j]
  • 边界
    d p [ i ] [ j ] = 1 , d p [ i ] [ i + 1 ] = ( S [ i ] = = S [ i + 1 ] ) ? 1 : 0 dp[i][j] = 1, dp[i][i + 1] = (S[i] == S[i+1])?1:0 dp[i][j]=1,dp[i][i+1]=(S[i]==S[i+1])?1:0
//边界
for(int i = 0; i < len; ++i)
{
    dp[i][i] = 1;
    if(i < len - 1)
    {
        if(S[i] == S[i + 1])
        {
            dp[i][i+1] = 1;
            ans = 2;
        }
    }
}
//状态转移方程
for(int L = 3; L <= len; ++L)
{
    for(int i = 0; i + L - 1 < len; ++i)
    {
        int j = i + L -1;
        if(S[i] == S[j] && dp[i+1][j-1] == 1)
        {
            dp[i][j] = 1;
            ans = L;
        }
    }
}

DAG最长路

DAG:有向无环图

  • 求整个DAG中的最长路径(即不固定起点跟终点)
  • 固定重点,求DAG的最长路径

问题:给定一个有向无环图,怎么样求解整个图的所有路径中权值之和最大的那条


背包问题

多阶段动态规划问题

背包问题和完全背包问题


总结

  • 最大连续子列和
    令 d p [ i ] 表 示 以 A [ i ] 作 为 末 尾 的 连 续 序 列 的 最 大 和 令dp[i]表示以A[i]作为末尾的连续序列的最大和 dp[i]A[i]
  • 最长不下降子序列(LIS)
    令 d p [ i ] 表 示 以 A [ i ] 结 尾 的 最 长 不 下 降 子 序 列 长 度 令dp[i]表示以A[i]结尾的最长不下降子序列长度 dp[i]A[i]
  • 最长公共子序列(LCS)
    令 d p [ i ] [ j ] 表 示 字 符 串 A 的 i 号 位 和 字 符 串 B 的 j 号 位 之 前 的 L C S 长 度 令dp[i][j]表示字符串A的i号位和字符串B的j号位之前的LCS长度 dp[i][j]AiBjLCS
  • 最长回文子串
    令 d p [ i ] [ j ] 表 示 S [ i ] 至 S [ j ] 所 表 示 的 字 串 是 否 是 回 文 子 串 令dp[i][j]表示S[i]至S[j]所表示的字串是否是回文子串 dp[i][j]S[i]S[j]
  • 数塔DP
    令 d p [ i ] [ j ] 表 示 从 第 i 行 第 j 个 数 字 出 发 的 到 达 最 底 层 的 所 有 路 径 上 所 能 够 得 到 的 最 大 和 令dp[i][j]表示从第i行第j个数字出发的到达最底层的所有路径上所能够得到的最大和 dp[i][j]ij
  • DAG最长路
    令 d p [ i ] [ j ] 表 示 从 i 号 顶 点 出 发 能 获 得 的 最 长 路 径 长 度 令dp[i][j]表示从i号顶点出发能获得的最长路径长度 dp[i][j]i
  • 01背包
    令 d p [ i ] [ v ] 表 示 令dp[i][v]表示 dp[i][v]
  • 完全背包
  • $$$$
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值