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;
}
未完待续…