动态规划之二

动态规划问题2

最长升子序问题

问题:有一个长度为n的数列 a 0 , a 1 , ⋯   , a n − 1 a_0,a_1,\cdots,a_{n-1} a0,a1,,an1.请求出这个序列中最长的上升子序列的长度. 上升子序列是指对于任意的 i < j i<j i<j都满足 a i < a j a_i < a_j ai<aj的子序列.

限制条件
1 ⩽ n ⩽ 1000 0 ⩽ a 1 ⩽ 1000000 1 \leqslant n \leqslant 1000 \\ 0 \leqslant a_1 \leqslant 1000000 1n10000a11000000

输入:

n = 5
a = {4,2,3,1,5}

输出:

3(a1,a2,a4构成的子序列2,3,5)

问题分析:

方法一

假设使用 dp[i] := 长度为i的序列的最长子序列长度.

该递推关系可以表示为;

  • 不包括 a i a_i ai时, 长度为 d p [ i − 1 ] dp[i-1] dp[i1]
  • 包括 a i a_i ai 时, 长度为 d p [ j ] + 1 , j < i , a j < a i dp[j] +1, j<i, a_j < a_i dp[j]+1,j<i,aj<ai,

d p [ 0 ] = 0 d p [ i + 1 ] = max ⁡ { d p [ i ] , d p [ j ] + 1 } , j < i + 1 , a j < a i + 1 \begin{aligned} &dp[0] = 0 \\ &dp[i+1] = \max \{ dp[i], dp[j]+1 \}, \qquad j<i+1, a_j <a_i+1 \end{aligned} dp[0]=0dp[i+1]=max{dp[i],dp[j]+1},j<i+1,aj<ai+1

代码如下:

int dp[MAX_N+1];

int n,a[MAX_N];

int maxList(){

    for(int i =0; i< n ; i++){
        dp[i+1] = dp[i];
        for(int j = 0;j<=i;j++){
            if(a[j]<a[i]){
                dp[i+1] = max(dp[i+1],dp[j]+1);
            }else{
                dp[i+1] = max(dp[i+1],1); // 如果a_j 不必 a_i 大,那么,包含a_i的长度只能是1.
            }
        }
    }
    return dp[n];
}

方法二

假设:
d p [ i ] : = 以 a i 为 末 尾 的 最 长 上 升 子 序 列 的 长 度 dp[i] := 以a_i 为末尾的最长上升子序列的长度 dp[i]:=ai

递推关系为:

  • 只包含a_i 的子序列
  • 在满足 j < i , a j < a i j<i, a_j<a_i j<i,aj<ai的前提下, 以 a j a_j aj 为结尾的上升子序列末尾加上 a i a_i ai 得到的子序列.

满足关系式为:

d p [ i ] = max ⁡ { 1 , d p [ j ] + 1 ∣ j < i 且 a j < a i } dp[i] =\max \{ 1, dp[j]+1 | j<i且 a_j < a_i \} dp[i]=max{1,dp[j]+1j<iaj<ai}

在此关系式中与以往的dp不同,这里的dp[0] 表示以 a 0 a_0 a0 结尾的数列,即长度为1,而不是0.

最终的结果也不是dp[n-1], 而是整个dp序列中的最大值.

代码为;

int maxList2(){
    int res = 0;
    for(int i =0; i< n; i++){
        dp[i] = 1;
        for(int j = 0; j<i;j++){
            if(a[j]<a[i]){
                dp[i] = max(dp[i],dp[j]+1);
            }
        }
    res = max(res,dp[i]);
    }
    return res;
}

方法三

定义:

d p [ i ] : = 长 度 为 i + 1 的 上 升 子 序 列 中 末 尾 元 素 的 最 小 值 ( 默 认 为 I N F ) dp[i] := 长度为i+1的上升子序列中末尾元素的最小值(默认为INF) dp[i]:=i+1(INF)

分析:

  • 最开始全部的dp[i] 初始化为INF
  • 对于每个 a j a_j aj , 如果 i = 0 i=0 i=0 或者 d p [ i − 1 ] < a j dp[i-1] < a_j dp[i1]<aj 的话, 就用 d p [ i ] = min ⁡ ( d p [ i ] , a j ) dp[i] = \min(dp[i] , a_j ) dp[i]=min(dp[i],aj) 进行更新
  • 最终找出使得dp[i] < INF 的最大的i+1就是结果

在这里插入图片描述

代码:

int maxList3(){
    fill(dp,dp+n, INF);
    for(int i =0 ;i<n; i++){
        *lower_bound(dp,dp+n, a[i]) = a[i];
        
    }
    return int(lower_bound(dp,dp+n,INF)-dp);

}

lower_bound()函数是一个STL函数, 利用二分查找,在有序序列a中找出指向 a i ⩾ k a_i \geqslant k aik a i a_i ai 的最小指针.
upper_bound()函数利用二分查找,在有序序列a中找出指向 a i > k a_i > k ai>k a i a_i ai 的最小指针.

计数问题的dp

划分数

题目:

有n个无区别的物品,将他们划分成不超过m组,求出划分方法数模M的余数.

限制条件:

1 ⩽ m ⩽ n ⩽ 1000 , 1 \leqslant m \leqslant n \leqslant 1000, 1mn1000,
2 ⩽ M ⩽ 10000 2 \leqslant M \leqslant 10000 2M10000

输入:

n =4
m = 3
M = 10000

输出:

4 (1+1+2=1+3=2+2=4)

这种问题被成为n的m划分

定义:

dp[i][j] := j的i划分总数

思考:

将j划分成i个,那么可以先取出k个,然后将剩下的 j-k个划分成[i-1]份,那么公式为:
d p [ i ] [ j ] = ∑ k = 0 j d p [ i − 1 ] [ j − k ] dp[i][j] = \sum_{k=0}^j dp[i-1][j-k] dp[i][j]=k=0jdp[i1][jk]

然而这样划分是不正确的,其中包含了重复的组合,例如1+2+1 和1+1+2.

  • 考虑n的m划分 a i ( ∑ i = 1 m a i = n } a_i(\sum_{i=1}^m a_i = n\} ai(i=1mai=n}
  • 如果对于每个i 都有 a i > 0 a_i >0 ai>0, 那么 { a i − 1 } \{ a_i-1 \} {ai1} 就对应了n-m的m划分.
  • 如果存在 a i = 0 a_i = 0 ai=0,那么就对应了n的m-1划分.

上述解释比较抽象,那么我们可以如下考虑:

上述问题可以类比为:将相同的小球装到m个相同的盘子中,可以有空盘子.

dp[m][n] : =将n个小球放入m个盘中.

  • 如果至少有空盘子, d p [ m ] [ n ] = d p [ m − 1 ] [ n ] dp[m][n] = dp[m-1][n] dp[m][n]=dp[m1][n]
  • 没有空盘子,就相当于预先在每个盘子中放入一个小球, 所以 d p [ m ] [ n ] = d p [ n − m ] dp[m][n] = dp[n-m] dp[m][n]=dp[nm]

所以我们得到递推公式为:

d p [ 0 ] [ j ] = 0 dp[0][j] = 0 dp[0][j]=0
d p [ m ] [ n ] = { d p [ m − 1 ] [ n ] , n < m d p [ m ] [ n − m ] + d p [ m − 1 ] [ n ] , n > = m \begin{aligned} dp[m][n] = \left \{ \begin{aligned} & dp[m-1][n] ,\qquad n<m \\ & dp[m][n-m] + dp[m-1][n],\qquad n>=m \end{aligned} \right. \end{aligned} dp[m][n]={dp[m1][n],n<mdp[m][nm]+dp[m1][n],n>=m

代码:

int parted(){
    fill(dp[0],dp[0]+n+1,0);
    dp[0][0] = 1;
    for (int i = 0; i < m; i++){
        for(int j =0; j<= n;j++){
            if(i>j){
                dp[i+1][j] = dp[i][j];
            }else{
                dp[i+1][j] = dp[i][j]+dp[i+1][j-i-1] % M;
            }
        }
    }
    return dp[m][n];
    
}

多重集组合数

题目:

有n种物品,第i 种物品有 a i a_i ai个. 不同种类的物品可以相互区分但是同种类的无法区分. 从这些物品

限制条件:

1 ⩽ n ⩽ 1000 1 \leqslant n \leqslant 1000 1n1000

1 ⩽ m ⩽ 1000 1 \leqslant m \leqslant 1000 1m1000

1 ⩽ a i ⩽ 1000 1 \leqslant a_i \leqslant 1000 1ai1000

2 ⩽ M ⩽ 10000 2 \leqslant M \leqslant 10000 2M10000

输入:

n = 3
m = 3
a = {1,2,3}

输出:

6 (0+0+3, 0+1+2, 0+2+1, 1+0+2, 1+1+1, 1+2+0)

定义:
d p [ i + 1 ] [ j ] : = 从 前 i 种 物 品 中 取 出 j 个 的 组 合 数 dp[i+1][j] := 从前i种物品中取出j个的组合数 dp[i+1][j]:=ij

  • 从前i-1种物品中取出j-k个
  • 从第i个物品中取出k个

可以得到递推关系式为:
d p [ i + 1 ] [ j ] = ∑ k = 0 min ⁡ ( j , a [ i ] ) d p [ i ] [ j − k ] dp[i+1][j] = \sum_{k=0}^{\min(j,a[i])}dp[i][j-k] dp[i+1][j]=k=0min(j,a[i])dp[i][jk]

可以进行化简操作:

∑ k = 0 min ⁡ ( j , a [ i ] ) d p [ i ] [ j − k ] ) = ∑ k = − 1 min ⁡ ( j − 1 , a [ i ] − 1 ) d p [ i ] [ j − 1 − k ] = ∑ k = 0 min ⁡ ( j − 1 , a [ i ] ) d p [ i ] [ j − 1 − k ] + d p [ i ] [ j ] − d p [ i ] [ j − 1 − a i ] = d p [ i + 1 ] [ j ] = d p [ i + 1 ] [ j − 1 ] + d p [ i ] [ j ] − d p [ i ] [ j − 1 − a 1 ] \begin{aligned} \sum_{k=0}^{\min(j,a[i])}dp[i][j-k]) &= \sum_{k=-1}^{\min(j-1,a[i]-1)}dp[i][j-1-k] \\ & = \sum_{k=0}^{\min(j-1,a[i])}dp[i][j-1-k]+dp[i][j] - dp[i][j-1-a_i] \\ & = dp[i+1][j] = dp[i+1][j-1] +dp[i][j] -dp[i][j-1-a_1] \end{aligned} k=0min(j,a[i])dp[i][jk])=k=1min(j1,a[i]1)dp[i][j1k]=k=0min(j1,a[i])dp[i][j1k]+dp[i][j]dp[i][j1ai]=dp[i+1][j]=dp[i+1][j1]+dp[i][j]dp[i][j1a1]


#include<iostream>

using namespace std;

#define MAX_N 1000
#define MAX_M 1000

int dp[MAX_N+1][MAX_M+1];

int n,m,M;
int a[MAX_N];

void init(){
    cin>>n>>m;
    for(int i =0;i<n;i++){
        cin>>a[i];
    }
    cin>>M;
}

void solve(){
    //dp[i][0] =1
    for(int i =0;i<=n;i++){
        dp[i][0]=1;
    }
    for(int i =0;i<n;i++){
        for(int j = 1;j<=m;j++){
            if(j-1 >= a[i]){
                dp[i+1][j] = (dp[i+1][j-1]+dp[i][j]-dp[i][j-1-a[i]]+M)%M;
            }else{
                 dp[i+1][j] = (dp[i+1][j-1]+dp[i][j])%M;
            }
        }
    }
    cout<<dp[n][m]<<endl;
}

int main(int argc, char const *argv[])
{
    init();
    solve();
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值