#线性Dp
##数字三角形
https://www.acwing.com/problem/content/900/
----c++版
正序dp
#include<iostream>
#include<algorithm>
using namespace std;
const int N=510,inf=1e9;
int n;
int a[N][N];
int f[N][N];
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++)
for(int j=1;j<=i;j++)
scanf("%d",&a[i][j]);
for(int i=0;i<=n;i++)
for(int j=0;j<=i+1;j++)
f[i][j]=-inf;
f[1][1]=a[1][1];
for(int i=2;i<=n;i++)
for(int j=1;j<=i;j++)
f[i][j]=max(f[i-1][j-1]+a[i][j],f[i-1][j]+a[i][j]);
int res=-inf;
for(int i=1;i<=n;i++)res=max(res,f[n][i]);
printf("%d",res);
return 0;
}
倒序dp
#include<bits/stdc++.h>
using namespace std;
const int N=510;
int f[N][N];
int n;
int main(){
cin>>n;
for(int i=1;i<=n;i++)
for(int j=1;j<=i;j++)
cin>>f[i][j];
for(int i=n;i>=1;i--)
for(int j=i;j>=1;j--)
f[i][j]=max(f[i+1][j],f[i+1][j+1])+f[i][j];
cout<<f[1][1]<<endl;
}
##摘花生
https://www.acwing.com/problem/content/1017/
----c++版
#include<iostream>
#include<algorithm>
using namespace std;
//集合:f[i,j]:所有从1,1走到i,j的路线
//属性:max
//状态计算:划分为从上面来的和从左边来的
// f[i,j]=max(f[i-1,j],f[i,j-1])+w[i,j];
const int N=110;
int n,m;
int w[N][N],f[N][N];
int main(){
int T;
scanf("%d",&T);
while(T--){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
scanf("%d",&w[i][j]);
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
f[i][j]=max(f[i-1][j],f[i][j-1])+w[i][j];
printf("%d\n",f[n][m]);
}
return 0;
}
##最低通行费
https://www.acwing.com/problem/content/1020/
----c++版
#include<iostream>
#include<algorithm>
using namespace std;
//2n-1推出不能走回头路
//本质上是摘花生的问题
//转化为摘花生问题来做
const int N=110,inf=1e9;
int n;
int f[N][N],w[N][N];
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
scanf("%d",&w[i][j]);
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)//求最小的时候,可能出现一些边界问题
if(i==1&&j==1)f[i][j]=w[i][j];
else{
f[i][j]=inf;
if(i>1)f[i][j]=min(f[i][j], f[i-1][j]+w[i][j]);//至少是第二行才会考虑向下走
if(j>1)f[i][j]=min(f[i][j], f[i][j-1]+w[i][j]);
}
printf("%d", f[n][n]);
return 0;
}
##方格取数
https://www.acwing.com/problem/content/1029/
----c++版
#include<iostream>
#include<algorithm>
using namespace std;
//可以使用f[i1,j1,i2,j2]来表示所有从(1,1)(1,1)分别走到(i1,j1)(i2,j2)的两条路径和的最大值
//如何处理“同一个格子不能重复选择”?
//由于只能向右和向下走,所以横纵坐标之和正好是步数
//只有在i1+j1==i2+j2时,两条路径的格子才可能重合
//换句话说,只有在步数相等时才可能重合
//我们可以设置一个k来表示当前走过的步数,则只需要i1,i2就能表示横纵坐标
//集合可以表示为f[k,i1,i2] , 属性是max
//状态的计算:集合可划分为4种,每条路径最后一步都有两种来源
// 第一种,两条都是向下走
// 表示为f[k-1,i1-1,i2-1],如果两条路径的下一步重合了,只需要加上w[i1,j1],
// 不重合则加w[i1,j1]与w[i2,j2]
const int N=15;
int n;
int w[N][N];
int f[N*2][N][N];
int main(){
scanf("%d",&n);
int a,b,c;
while(cin>>a>>b>>c,a||b||c)w[a][b]=c;
for(int k=2;k<=n+n;k++)//k从2开始,因为1+1=2
for(int i1=1;i1<=n;i1++)
for(int i2=1;i2<=n;i2++){
int j1=k-i1,j2=k-i2;
if(j1>=1&&j1<=n&&j2>=1&&j2<=n){//细节
int t=w[i1][j1];
if(i1!=i2)t+=w[i2][j2];
int &x=f[k][i1][i2];
x=max(x,f[k-1][i1-1][i2-1]+t);
x=max(x,f[k-1][i1-1][i2]+t);
x=max(x,f[k-1][i1][i2-1]+t);
x=max(x,f[k-1][i1][i2]+t);
}
}
printf("%d",f[n+n][n][n]);
return 0;
}
##传纸条
https://www.acwing.com/problem/content/277/
----c++版
#include<iostream>
#include<algorithm>
using namespace std;
const int N=60;
int n,m;
int w[N][N];
int f[N+N][N][N];
int main(){
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
scanf("%d",&w[i][j]);
for(int k=2;k<=n+m;k++)
for(int i1=1;i1<=n;i1++)
for(int i2=1;i2<=n;i2++){
int j1=k-i1,j2=k-i2;
if(j1>=1&&j1<=m&&j2>=1&&j2<=m){
int t=w[i1][j1];
if(i1!=i2)t+=w[i2][j2];
int &x=f[k][i1][i2];
x=max(x,f[k-1][i1-1][i2-1]+t);
x=max(x,f[k-1][i1-1][i2]+t);
x=max(x,f[k-1][i1][i2-1]+t);
x=max(x,f[k-1][i1][i2]+t);
}
}
printf("%d",f[n+m][n][n]);
return 0;
}