逆推dp经典题目:数字三角形的折叠版
为什么这么说?
因为我们会发现:除了每一次都特判一下是否转换行号以外,剩下的思想没什么不同。
没看题目的看这里
先定义:n,m
是步骤数目,小组数目work[i][j]
表示第i
个小组第j
步需要的天数f[i][j]
表示当前第i
个小组第j
步的最优天数
首先我们先看到这个题说是要求最小天数
然后我们知道这个最小天数是由原先的两个最小天数分别加上当前小组工作天数,然后二者求最小值(因为前面的两个子状态——天数决定了后面的状态——当前最小天数)
这中间告诉我们当最下面的小组还想向下找小组,就返回最上面的小组1。
所以我们得出几个推论:
- 因为最后不同的小组会得到不同的值,所以我们应当求出最后一步中的最小天数值(最小值跑最后一列一遍)
- 普通(指的是不看第三点)的dp状态转移方程就是
\[f[i][j]=min(f[i-1][j-1]+work[i][j],f[i][j-1]+work[i][j])
\]
\]
- 逆着想,当
i=1
的时候,决定当前最小天数的是i=n
和i=1
两个子状态,所以当i=1
的时候,转移的i-1
就应当变成n
。
于是得出下面的递推式:
\[f[i][j]=min(f[i!=1?i-1:n][j-1]+work[i][j],f[i][j-1]+work[i][j])
\]
\]
这样这个题的思路就做完了。
代码好说:
#include <iostream>
#include <cstdio>
#define fin cin//测试来着
#define fout cout
using namespace std;
typedef long long int lli;
const int maxn=2000,maxm=2000;
lli f[maxm+1][maxn+1],work[maxm+1][maxn+1];
//f[i][j]表示当前第i个小组第j步的最优结果
lli n,m,ans=2147483647;
inline lli max(lli a,lli b) {
return a>=b?a:b;
}
inline lli min(lli a,lli b) {
return a<=b?a:b;
}
int main() {
fin>>n>>m;
for (register int i=1; i<=m; i++) {
for (register int j=1; j<=n; j++) {
fin>>work[i][j];
}
}
//上面是按照表读的
for (register int i=1; i<=m; i++) {
f[i][1]=work[i][1];
}
//第一步就是原先第一列的内容
for (register int i=2; i<=n; i++) {
//从第二步骤开始推
for (register int j=1; j<=m; j++) {
//j是当前到了第几个小组
if (j==1) {
f[j][i]=min(f[m][i-1]+work[j][i],f[j][i-1]+work[j][i]);
}
else {
f[j][i]=min(f[j-1][i-1]+work[j][i],f[j][i-1]+work[j][i]);
}
}
}
for (register int i=1; i<=m; i++) {
ans=min(ans,f[i][n]);
}
cout<<ans;
return 0;
}