DP基础题型及解题思路总结

DP基础题型及解题思路总结

一、解题一般思路

①、状态表示:

( 1 ) 、 集 合 : 合 法 的 所 有 方 案 的 集 合 (1)、集合:合法的所有方案的集合 (1)

( 2 ) 、 属 性 : M a x / M i n / C o u n t (2)、属性:Max/Min/Count (2)Max/Min/Count

②、状态计算—集合的划分:

划 分 依 据 — — 最 后 一 个 不 同 的 点 划分依据——最后一个不同的点


二、01背包问题

题目:

有 N 件物品和一个容量是 V 的背包。每件物品只能使用一次。

第 i 件物品的体积是 vi,价值是 wi。

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

输入格式
第一行两个整数,N,V,用空格隔开,分别表示物品数量和背包容积。

接下来有 N 行,每行两个整数 vi,wi,用空格隔开,分别表示第 i 件物品的体积和价值。

输出格式
输出一个整数,表示最大价值。

数据范围
0<N,V≤1000
0<vi,wi≤1000
输入样例
4 5
1 2
2 4
3 4
4 5
输出样例:
8

分析:
在这里插入图片描述
① 、 状 态 表 示 : 数 据 范 围 [ 0 , 1000 ] , 意 味 着 状 态 中 可 能 含 1 或 者 2 个 变 量 , 用 d p [ i ] [ j ] 来 表 示 考 虑 前 i 个 物 品 , 物 品 总 体 积 不 超 过 j 的 方 案 中 的 最 大 值 。 ①、状态表示:数据范围[0,1000],意味着状态中可能含1或者2个变量,用dp[i][j]来表示考虑前i个物品,物品总体积不超过j的方案中的最大值。 [0,1000]12dp[i][j]ij

② 、 状 态 计 算 : 对 每 一 个 物 品 而 言 , 只 有 取 和 不 取 两 种 状 态 。 考 虑 第 i 件 物 品 : ②、状态计算:对每一个物品而言,只有取和不取两种状态。考虑第i件物品: i

Ⅰ 、 不 取 第 i 件 物 品 , 意 味 着 在 前 i − 1 件 物 品 已 经 取 到 j 体 积 的 物 品 , 最 大 值 是 d p [ i − 1 ] [ j ] 。 Ⅰ、不取第i件物品,意味着在前i-1件物品已经取到j体积的物品,最大值是dp[i-1][j]。 ii1jdp[i1][j]

Ⅱ 、 取 第 i 件 物 品 , 意 味 着 在 前 i − 1 件 物 品 已 经 取 到 j − v [ i ] 体 积 的 物 品 , 最 大 值 是 从 前 i − 1 件 物 品 中 取 j − v [ i ] 体 积 物 品 的 最 大 价 值 + 第 i 件 物 品 的 价 值 。 Ⅱ、取第i件物品,意味着在前i-1件物品已经取到j-v[i]体积的物品,最大值是从前i-1件物品中取j-v[i]体积物品的最大价值+第i件物品的价值。 ii1jv[i]i1jv[i]+i

于 是 得 到 状 态 转 移 方 程 : d p [ i ] [ j ] = m a x ( d p [ i − 1 ] [ j ] , d p [ i − 1 ] [ j − v [ i ] ] + w [ i ] ) 。 于是得到状态转移方程:dp[i][j]=max(dp[i-1][j],dp[i-1][j-v[i]]+w[i])。 :dp[i][j]=max(dp[i1][j],dp[i1][jv[i]]+w[i])

注 意 : d p [ i − 1 ] [ j − v [ i ] ] + w [ i ] 只 有 在 j > = v [ i ] 的 情 况 下 才 会 考 虑 , 代 码 如 下 : 注意:dp[i-1][j-v[i]]+w[i]只有在j>=v[i]的情况下才会考虑,代码如下: dp[i1][jv[i]]+w[i]j>=v[i]

代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define ll long long
#define inf 0x3f3f3f3f
using namespace std;
const int maxn=1e3+5;
int N,V,v[maxn],w[maxn];
int dp[maxn][maxn];//dp[i][j] 前i件物品中体积不超过j的最大价值
int main()
{
    cin>>N>>V;
    for(int i=1;i<=N;i++)
        scanf("%d%d",&v[i],&w[i]);

    for(int i=1;i<=N;i++)
        for(int j=1;j<=V;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]);
        }

    cout<<dp[N][V]<<endl;
    return 0;
}

改进:

从 上 述 转 移 方 程 中 我 们 发 现 , 前 i 件 的 最 大 价 值 仅 与 前 i − 1 件 的 最 大 价 值 有 关 , 因 此 考 虑 优 化 d p 数 组 空 间 , 第 一 维 空 间 可 以 直 接 删 去 。 得 到 : 从上述转移方程中我们发现,前i件的最大价值仅与前i-1件的最大价值有关,因此考虑优化dp数组空间,第一维空间可以直接删去。得到: ii1dp

    for(int i=1;i<=N;i++)
        for(int j=v[i];j<=V;j++)
        {
             dp[j]=max(dp[j],dp[j-v[i]]+w[i]);
        }

注 意 : 在 原 转 移 方 程 : d p [ i ] [ j ] = m a x ( d p [ i − 1 ] [ j ] , d p [ i − 1 ] [ j − v [ i ] ] + w [ i ] ) 中 , d p [ i − 1 ] [ j − v [ i ] ] 是 在 d p [ i ] [ j ] 之 前 被 计 算 出 的 , 因 此 , 对 上 述 优 化 过 的 循 环 中 , 要 将 第 二 层 循 环 j 从 大 到 小 枚 举 。 注意:在原转移方程:dp[i][j]=max(dp[i-1][j],dp[i-1][j-v[i]]+w[i])中,dp[i-1][j-v[i]]是在dp[i][j]之前被计算出的,因此,对上述优化过的循环中,要将第二层循环j从大到小枚举。 dp[i][j]=max(dp[i1][j],dp[i1][jv[i]]+w[i])dp[i1][jv[i]]dp[i][j]j

01 背 包 问 题 一 维 写 法 : 01背包问题一维写法: 01

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define ll long long
#define inf 0x3f3f3f3f
using namespace std;
const int maxn=1e3+5;
int N,V,v,w;
int dp[maxn];//dp[i]所拿物品体积不超过i的最大价值
int main()
{
    cin>>N>>V;
    for(int i=1;i<=N;i++)
    {
        scanf("%d%d",&v,&w);
        for(int j=V;j>=v;j--)//从大到小
            dp[j]=max(dp[j],dp[j-v]+w);
    }

    cout<<dp[V]<<endl;
    return 0;
}

三、摘花生

题目:

Hello Kitty想摘点花生送给她喜欢的米老鼠。

她来到一片有网格状道路的矩形花生地(如下图),从西北角进去,东南角出来。

地里每个道路的交叉点上都有种着一株花生苗,上面有若干颗花生,经过一株花生苗就能摘走该它上面所有的花生。

Hello Kitty只能向东或向南走,不能向西或向北走。

问Hello Kitty最多能够摘到多少颗花生。

1.gif

输入格式
第一行是一个整数T,代表一共有多少组数据。

接下来是T组数据。

每组数据的第一行是两个整数,分别代表花生苗的行数R和列数 C。

每组数据的接下来R行数据,从北向南依次描述每行花生苗的情况。每行数据有C个整数,按从西向东的顺序描述了该行每株花生苗上的花生数目M。

输出格式
对每组输入数据,输出一行,内容为Hello Kitty能摘到得最多的花生颗数。

数据范围
1≤T≤100,
1≤R,C≤100,
0≤M≤1000
输入样例:
2
2 2
1 1
3 4
2 3
2 3 4
1 6 5
输出样例:
8
16

分析:
在这里插入图片描述
代码:

//AcWing-1015. 摘花生
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define ll long long
#define inf 0x3f3f3f3f
using namespace std;
const int N=105;
int T,R,C;
int mp[N][N];
int dp[N][N];
int main()
{
    cin>>T;
    while(T--)
    {
        memset(mp,0,sizeof(mp));
        cin>>R>>C;
        for(int i=1;i<=R;i++)
            for(int j=1;j<=C;j++)
                scanf("%d",&mp[i][j]);

        /*
        dp[1][1]=mp[1][1];
        for(int i=1;i<=R;i++)
            for(int j=1;j<=C;j++)
                if(i==1&&j==1) continue;
                else dp[i][j]=max(dp[i-1][j]+mp[i][j],dp[i][j-1]+mp[i][j]);*/

		//上述dp过程的优化
        for(int i=1;i<=R;i++)
            for(int j=1;j<=C;j++)
                dp[i][j]=max(dp[i-1][j],dp[i][j-1])+mp[i][j];

        cout<<dp[R][C]<<endl;
    }

    return 0;
}

四、最长上升子序列

题目:

给定一个长度为N的数列,求数值严格单调递增的子序列的长度最长是多少。

输入格式
第一行包含整数N。

第二行包含N个整数,表示完整序列。

输出格式
输出一个整数,表示最大长度。

数据范围
1≤N≤1000,
−109≤数列中的数≤109
输入样例:
7
3 1 2 1 8 5 6
输出样例:
4

分析:
在这里插入图片描述
① 、 状 态 表 示 : d p [ i ] : 以 第 i 个 数 结 尾 的 严 格 单 调 上 升 子 序 列 的 集 合 ( 若 考 虑 前 i 个 数 字 上 升 最 大 值 会 比 较 麻 烦 ) 。 ①、状态表示:dp[i]:以第i个数结尾的严格单调上升子序列的集合(若考虑前i个数字上升最大值会比较麻烦)。 dp[i]:i(i)

② 、 状 态 计 算 : 考 虑 最 后 一 个 “ 不 同 点 ” : 由 于 对 于 d p [ i ] 集 合 中 , 最 后 一 个 数 字 都 相 同 , 故 考 虑 倒 数 第 二 个 数 字 。 倒 数 第 二 个 数 字 有 i 种 情 况 , 可 能 不 存 在 ( d p [ i ] = 1 , 只 有 一 个 数 字 ) , 也 可 能 是 第 1 , 2 , . . . , i − 1 个 数 字 。 ②、状态计算:考虑最后一个“不同点”:由于对于dp[i]集合中,最后一个数字都相同,故考虑倒数第二个数字。\\倒数第二个数字有i种情况,可能不存在(dp[i]=1,只有一个数字),也可能是第1,2,...,i-1个数字。 dp[i]i(dp[i]=1,)1,2,...,i1

故 d p [ i ] = m a x ( d p [ i ] , d p [ j ] + 1 ) , j ∈ [ 0 , i − 1 ] 。 故dp[i]=max(dp[i],dp[j]+1),j∈[0,i-1]。 dp[i]=max(dp[i],dp[j]+1),j[0,i1]

注 意 边 界 问 题 : 每 一 遍 更 新 d p [ i ] 时 要 初 始 话 为 1 , 也 就 是 倒 数 第 二 个 数 字 不 存 在 的 情 况 。 注意边界问题:每一遍更新dp[i]时要初始话为1,也就是倒数第二个数字不存在的情况。 dp[i]1

最 后 取 所 有 d p 结 果 中 的 最 大 值 即 可 。 最后取所有dp结果中的最大值即可。 dp

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#define ll long long
#define inf 0x3f3f3f3f
using namespace std;
int N;
ll a[1005];
int dp[1005];//dp[i]以a[i]结尾的子序列的最大值
int main()
{
    cin>>N;
    for(int i=1;i<=N;i++)
        cin>>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[i]=max(dp[i],dp[j]+1);//a[i]是max(dp[1~i-1])+1
        }
    }

    int ans=0;
    for(int i=1;i<=N;i++)
        ans=max(ans,dp[i]);

    cout<<ans<<endl;
    return 0;
}
  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值