动态规划

1.什么是DP?

我们用一个例题来理解什么时动态规划
三角形取数
假如你在如下正整数三角形的最下方
你每次可以向正上方或者左上方走一格,直达最上方
求你经过的数字的最大和
1
1 4
5 1 4
1 9 1 9
8 1 0 8 9
思路
每个位置可由其右下方的位置和正下方的位置到达
设你在i行j列时能取得数的最大值为f[i][j]
发现f[i][j]=max(f[i+1][j],f[i+1][j+1])+a[i][j]
有了递推公式我们就能写一个递归

for (int i = n; i >= 1; i--)
{
	for (int j = 1; j <= i; j++)
	{
		int cur = 0;
		if (i + 1 <= n)
		{
			cur = max(f[i + 1][j], f[i + 1][j + 1]);
			f[i][j] = a[i][j] + cur;
		}
	}
}
ans = f[1][1];

看完例子,那么什么是动态规划呢?

定义

动态规划就是将一个问题分解成多个子问题
满足无后效性和最优子结构性质
无后效性:小问题的最优解取法不影响大问题求解
最优子结构:大问题的最优解可由小问题的最优解得到
状态转移:由一些状态到另外一些状态
状态转移方程:状态转移的转移式,例如f[i][j]=max(f[i+1][j],f[i+1][j+1])+a[i][j]

01背包问题

与背包问题不同的是,01背包中的物品只能由全纳入背包和不拿两种状态,每种物品只有一件
你有一个容量为m的背包
有n件物品,每件质量为w[i],价值为a[i]
求放入背包的物品的最大总价值
思路:动态规划问题的主要解法就是找到状态转移方程
令处理第i件物品和总质量为c的最大价值为dp[i][c]
对于第i件物品,如果将其放入重量为c-w[i]的背包中
dp[i][c]=dp[i-1][c-w[i]]+a[i]
对于第i件物品,如果不能将其放入重量为c-w[i]的背包中
dp[i][c]=dp[i-1][c]
故可以得到状态转移方程dp[i][c]=max(dp[i-1][c-w[i]]+a[i],dp[i-1][c])

在这里插入图片描述
优化:
我们发现dp[i-1][j]只用于dp[i][j]与dp[i][j+ 𝑤[i]]的比较
从j=m起倒序遍历,比较dp[i-1][j]后直接更新
因此可省略[i]维
从j=0起正序遍历,将导致dp[i][j]与dp[i][j-w[i]]比较
空间复杂度O(nm)→O(n)

代码可以优化为:
在这里插入图片描述

完全背包

完全背包和01背包只有一个不同,一个物品可以拿多次
下面我们看一个例题

洛谷P1616
在这里插入图片描述
代码如下:

#include<iostream>
#include<cstdio>
#define int long long
using namespace std;
const int N = 1e4 + 5, M = 1e7 + 5;
int n, m, w[N], v[N], f[M];
signed main() {
	scanf("%lld%lld", &m, &n);
	for (int i = 1; i <= n; i++)
		scanf("%lld%lld", &w[i], &v[i]);
	for (int i = 1; i <= n; i++)
		for (int j = w[i]; j <= m; j++)
			f[j] = max(f[j], f[j - w[i]] + v[i]);
	printf("%lld", f[m]);
	return 0;
}

2.区间DP

同样我们先看一个引例
合并石子
在这里插入图片描述
思路:
合并的新石子堆一定来自起初的连续石子堆
[L,R]区间组成的石子堆来自[L,K]与[K+1,R]两个石子堆
令合并[L,R]区间的最小代价为dp[L][R]
合并[L,R]区间的代价是固定的
dp[L][R]=sum[L][R]+min(dp[L][K]+dp[K+1][R]) (L<=K<=R-1)
代码
在这里插入图片描述
区间DP
在这里插入图片描述
例题洛谷P1880在这里插入图片描述
思路:
这题的石子变成了环形,有什么不同呢?像上一道题,我们是从A,B,C,D中寻求最优解,但是由于石子变成了环形,所以还可以有B,C,D,A||C,D,A,B||D,A,B,C四种方法,也就是说我们还要遍历石子堆的排列,这样时间复杂度就变成了O(N^4),这样大的时间复杂度肯定会超时的,那么我们就要进行优化
优化方法:我们可以将一个环变成一列,我们观察A,B,C,D的排列发现他们的相对位置是不会变的,所以在A,B,C,D环上寻找最优解就是在A,B,C,D,A,B,C这样一个长度为7的区间上寻找长度为4的最小价值

代码如下

#include<iostream>  
#include<cstdio>  
#include<cmath>  
using namespace std;
int n, minl, maxl, f1[300][300], f2[300][300], num[300];
int s[300];
inline int d(int i, int j) { return s[j] - s[i - 1]; }
//转移方程:f[i][j] = max(f[i][k]+f[k+1][j]+d[i][j];
int main()
{
    scanf("%d", &n);
    for (int i = 1; i <= n + n; i++) 
    {
        scanf("%d", &num[i]);
        num[i + n] = num[i];
        s[i] = s[i - 1] + num[i];
    }
    for (int p = 1; p < n; p++)
    {
        for (int i = 1, j = i + p; (j < n + n) && (i < n + n); i++, j = i + p)
        {
            f2[i][j] = 999999999;
            for (int k = i; k < j; k++)
            {
                f1[i][j] = max(f1[i][j], f1[i][k] + f1[k + 1][j] + d(i, j));
                f2[i][j] = min(f2[i][j], f2[i][k] + f2[k + 1][j] + d(i, j));
            }
        }
    }
    minl = 999999999;
    for (int i = 1; i <= n; i++)
    {
        maxl = max(maxl, f1[i][i + n - 1]);
        minl = min(minl, f2[i][i + n - 1]);
    }
    printf("%d\n%d", minl, maxl);
    return 0;
}

未完待续…

3.树形DP

4.状压DP

5.数位DP

6.期望DP

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

青云遮夜雨

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值