动态规划——数字三角形模型

gloria-wmh / 2024-08-30 / 原文

数字三角形模型

母题 : 数字三角形

32c858b5886d86b8db6d2cc6d133f9f5

思路

​ 集合 f [i] [j] 表示所有从起点走到(i,j)的路径

​ 属性 f [i] [j] 存的数是集合中所有路径和的最大值

​ 状态计算:对于每一条从起点到 ( i , j ) 的路径,其要么是从左上方来的,要么是从右上方来的。

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e5+10;
int n;
int a[510][510];
int f[510][510];
signed main(){
    ios::sync_with_stdio(false);
    cin.tie(0),cout.tie(0);
    cin>>n;
    for(int i=1;i<=n;i++)
        for(int j=1;j<=i;j++) cin>>a[i][j];
    for(int i=0;i<=n;i++)
        for(int j=0;j<=i+1;j++) f[i][j]=-1e9;
    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],f[i-1][j-1])+a[i][j];
    int mmax=-1e9;
    for(int i=1;i<=n;i++) mmax=max(mmax,f[n][i]);
    cout<<mmax<<endl; 
    return 0;
}

子题1 : 摘花生

63eb5b0b75f3748d4d2178d4fd9eb7b6

思路

​ 基本和母题的思路一致,转移方程一致

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e3+10;
int f[N][N];
int w[N][N];
void solve()
{
    int n,m;
    cin>>n>>m;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++)
            cin>>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];
    }
    cout<<f[n][m]<<endl;
}
signed main(){
    ios::sync_with_stdio(false);
    cin.tie(0),cout.tie(0);
    int T=1;
    cin>>T;
    while(T--)
    {
        solve();
    }
    return 0;
}

子题2 : 最低通行费

04e08eef2436637e0b47c3bcf7701441

思路

​ 因为必须在(2*N-1)个单位时间穿越过来,所以只能向右或向左移动

​ 集合 f [i] [j] 表示走到第i行第j列的所有方案

​ 属性 走过路线的总价值最小值Min

​ 转移方程

​ 从左面走过来 从右面走过来

​ f [i] [j] = f [i] [j-1] +w f [i] [j] = f [i-1] [j] +w

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e3+10;
const int inf = 1e9;
int w[N][N];
int f[N][N];
void solve()
{
    int n;
    cin>>n;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++)
            cin>>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]);
            }
        }
    }
    cout<<f[n][n]<<endl;
}
signed main(){
    ios::sync_with_stdio(false);
    cin.tie(0),cout.tie(0);
    int T=1;
    // cin>>T;
    while(T--)
    {
        solve();
    }
    return 0;
}

子题3 :方格取数

78ab8083b67d851befb40cbe03da79e1

思路

​ 和之前的题不同是本题需要走两边

​ 如果只走一次的话,求从起点到终点的最大权值和应该为:f[i] [j] = max(f[i-1] [j], f[i][j-1])+w[i] [j]; 即f[i][j]表示从起点走到(i,j)的最大权值和,

​ 而本题是从起点走两次走到终点的最大权值和,即状态表示为:f[i1] [j1] [i2] [j2];这里,我们考虑假设两条路线是同时走的,则表示为:所有从起点(1,1)(1,1)出发,然后分别走到(i1,j1),(i2,j2)的最大权值和

​ 我们可以进行 三维优化

​ 状态表示 f [k] [x1] [x2]

​ 集合 同时走K步,所有从(1,1),(1,1)走到 (x1,y1),(x2,y2) 获得花生的数目

​ 属性 Max

​ 状态计算 左 左 f [k-1] [x1-1] [x2-1]

​ 左 上 f [k-1] [x1-1] [x2]

​ 上 左 f [k-1] [x1] [x2-1]

​ 上 上 f [k-1] [x1] [x2]

特判 两个点是否重合 即 x1==x2 如果重合只加w [x1] [j1] 如果不重合加 w [x1] [j1] +w [x2] [j2]

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=15;
int w[N][N];
int f[N*2][N][N];
void solve()
{
    int n; cin>>n;
    int a,b,c;
    while(cin>>a>>b>>c,a||b||c) w[a][b]=c;
    for(int k=2;k<=n*2;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<=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);
                }
            }
        }
    }
    cout<<f[n*2][n][n];
}
signed main(){
    ios::sync_with_stdio(false);
    cin.tie(0),cout.tie(0);
    int T=1;
    // cin>>T;
    while(T--)
    {
        solve();
    }
    return 0;
}

子题4 : 传纸条

cbadeb0cc9f4ad2230f4b87f3e5e2ea8

思路

​ 可以思考成和方格取数 相同的代码逻辑,一个从上向下走,一个从下向上走,可以理解为两条路从上向下走,加上其限制条件

​ 状态表示 f [k] [x1] [x2]

​ 集合 同时走K步,所有从(1,1),(1,1)走到 (x1,y1),(x2,y2) 得到权值的最大值

​ 状态计算

​ 左 左 f [k-1] [x1-1] [x2-1]

​ 左 上 f [k-1] [x1-1] [x2]

​ 上 左 f [k-1] [x1] [x2-1]

​ 上 上 f [k-1] [x1] [x2]

注意 不能走到一个格子里 (和方格取数不同的地方) ,因为最优解一定不会相交,一定可以绕开这个点向其他地方走

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=55;
int w[N][N];
int f[N*2][N][N];       //表示两条路径分别到达(i,k-i),(j,k-j);
void solve()
{
    int n,m; cin>>n>>m;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++)
            cin>>w[i][j];
    }
    for(int k=2;k<=n+m;k++){
        for(int i=1;i<k;i++){
            for(int j=1;j<k;j++){
                int &v=f[k][i][j];
                int tmp=w[i][k-i];
                if(i!=j) tmp+=w[j][k-j];
                v=max(v,f[k-1][i-1][j]);
                v=max(v,f[k-1][i-1][j-1]);
                v=max(v,f[k-1][i][j-1]);
                v=max(v,f[k-1][i][j]);
                v+=tmp;
            }
        }
    }
    cout<<f[n+m][n][n];
}
signed main(){
    ios::sync_with_stdio(false);
    cin.tie(0),cout.tie(0);
    int T=1;
    // cin>>T;
    while(T--)
    {
        solve();
    }
    return 0;
}