『学习笔记』动态规划基础
先来看一道题:有三种面值的硬币,分别为 \(1,5,10\) 元。求一种选择方案使得恰好能凑成 \(s\) 元并使得选择的硬币数量最少,输出最少的硬币数量。
显然可以用贪心做,先选择最多的 \(10\) 元硬币,再选 \(5\) 元,最后选择 \(1\) 元。
但如果把 \(10\) 元硬币都改成 \(11\) 元硬币,还可以贪吗?
答案是否定的。
比较明显的一个反例就是凑 \(15\) 元。
如果用贪心的思路,那么会先选一个面值为 \(11\) 的硬币,然后发现不能选面值为 \(5\) 的硬币了,只能选四个面值为 \(1\) 的硬币。一共选了 \(5\) 枚硬币。
可是正解是选 \(3\) 枚面值为 \(5\) 的硬币,贪心显然出错了。
这时候就要用到动态规划思想了。
什么是动态规划
根据上面的例子,我们可以发现要凑出每种钱数 \(money\) 都可以从凑 \(money-1,money-5,money-11\) 的解中求出。因为要求 \(money\) 元的解,可以通过选择最后选择的一个硬币的面值来求。
若设 \(a_i\) 为凑出 \(i\) 元钱的最少使用硬币数,那么 \(a_i=\min\{a_{i-1},a_{i-5},a_{i-11}\}+1\)。
这种思路是用已知的解求更大的解,也就是用小问题的解来求大问题的解,这种思想被称之为动态规划。
如果用这种思想来解决上面这种硬币问题,那么无论是什么样的面值的硬币,都可以通过之前已经求出的小问题的解求出大问题的解。
设有 \(m\) 种面值的硬币,面值分别为 \(w_1,w_2,\dots,w_m\),那么 \(a_i=\min\{a_{i-w_1},a_{i-w_2},\dots,a_{i-w_m}\}+1\)。
我们称 \(a\) 为状态,\(a_i=\min\{a_{i-w_1},a_{i-w_2},\dots,a_{i-w_m}\}+1\) 为状态转移方程。
动态规划还有一个重要的点就是初始条件,例如这个问题中,\(a_1=1\),这样才能依次得到问题的解。
P1216 [USACO1.5][IOI1994]数字三角形 Number Triangles
题目大意
给定一个有 \(r\) 行的数字金字塔 \(a\),请求出从最高点到底部任意点结束的所有路径中,经过的数字最大的和。
7
3 8
8 1 0
2 7 4 4
4 5 2 6 5
上面的样例中,\(7 \rightarrow 3 \rightarrow 8 \rightarrow 7 \rightarrow 5\) 的路径的和最大,为 \(30\)。
思路
先来考虑状态设计,很容易想到可以定义 \(f_{i,j}\) 为从最高点到第 \(i\) 行第 \(j\) 列的路径和最大值,最终的答案就是 \(\max\{f_{r,1},f_{r,2},\dots,f_{r,r}\}\)。
那么再考虑转移方程,\(f_{i,j}\) 只能从 \(f_{i-1,j-1}\) 和 \(f_{i-1,j}\) 过来,那么肯定要选择两者中最大的,也就是从最高点到 \(a_{i-1,j-1}\) 和 \(a_{i-1,j}\) 的路径最大和中取较大值。
那么转移方程就是 \(f_{i,j}=\max\{f_{i-1,j-1},f_{i-1,j}\}+a_{i,j}\),需要加上 \(a_{i,j}\),因为求的是路径和。
就这么简单。
代码
#include <iostream>
using namespace std;
template<typename T=int>
inline T read(){
T X=0; bool flag=1; char ch=getchar();
while(ch<'0' || ch>'9'){if(ch=='-') flag=0; ch=getchar();}
while(ch>='0' && ch<='9') X=(X<<1)+(X<<3)+ch-'0',ch=getchar();
if(flag) return X;
return ~(X-1);
}
template<typename T=int>
inline void write(T X){
if(X<0) putchar('-'),X=~(X-1);
T s[20],top=0;
while(X) s[++top]=X%10,X/=10;
if(!top) s[++top]=0;
while(top) putchar(s[top--]+'0');
putchar('\n');
}
const int N=1e3+5;
int r,ans;
int f[N][N];
int main(){
r=read();
for(int i=1; i<=r; i++){
for(int j=1; j<=i; j++){
f[i][j]=read();
}
}
for(int i=1; i<=r; i++){
for(int j=1; j<=i; j++){
f[i][j]+=max(f[i-1][j-1],f[i-1][j]);
}
}
for(int i=1; i<=r; i++) ans=max(ans,f[r][i]);
write(ans);
return 0;
}