算法学习笔记-动态规划常用模板

在线体验各类最新模型,更有模型 免费Token 额度领取!
立即体验
简介: 本文总结了常见动态规划问题的模板与解法,包括: 背包问题系列:01背包、完全背包、多重背包(基础与优化)、分组背包问题,给出状态转移方程与代码实现。 线性DP问题:数字三角形、最长上升子序列(基础与优化)、最长公共子序列、编辑距离问题,详细说明递推思路。 区间DP问题:合并石子问题,分析区间合并的最优子结构。 计数DP问题:整数划分问题,介绍拆分数计算的动态规划方法。 树形DP问题:没有上司的舞会,展示树上状态转移的处理方法。 记忆化搜索问题:滑雪问题,演示如何通过缓存优化暴力搜索。 每个问题都包含题目描述

 背包问题模板

01背包问题

https://www.acwing.com/problem/content/2/

问题叙述:有N件物品和一个容量是V的背包,每个物品只能使用一次。第i件物品体积为v,价值为w,求将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大,输出最大价值

思路:DP数组中DP[j]表示背包承重为j时的最大价值。两重循环递推,第一重循环遍历每个物品,第二重循环逆序遍历承重。对于每一个承重,都有选和不选第i的问题的抉择。

代码:

#include<bits/stdc++.h>
using namespace std;
const int N=1010;
int n,m;
int v[N],w[N];
int dp[N];
int main()
{
  cin.tie(0);
  cout.tie(0);
  ios::sync_with_stdio(0);
    cin>>n>>m;
    for(int i=1;i<=n;i++) cin>>v[i]>>w[i];
    for(int i=1;i<=n;i++){
        for(int j=m;j>=v[i];j--){
            dp[j]=max(dp[j],dp[j-v[i]]+w[i]);
        }
    }
    
    cout<<dp[m]<<endl;
  return 0;
}

image.gif

完全背包问题

https://www.acwing.com/problem/content/3/

题目叙述:有N种物品和容量是V的背包,每种物品无限件使用。第i种物品体积为v,价值是w,求解将哪些物品装入背包,可使得物品的总体积不超过背包容量,且总价值最大。

思路:DP数组中DP[j]表示背包承重为j时的最大价值。两重循环递推,第一重循环遍历每个物品,第二重循环正序遍历承重。对于每一个承重,都有选和不选第i的问题的抉择。

代码:

#include<bits/stdc++.h>C
using namespace std;
const int N=1010;
int n,m;
int v[N],w[N];
int dp[N];
int main()
{
  cin.tie(0);
  cout.tie(0);
  ios::sync_with_stdio(0);
    cin>>n>>m;
    for(int i=1;i<=n;i++) cin>>v[i]>>w[i];
    for(int i=1;i<=n;i++){
        for(int j=v[i];j<=m;j++){
            dp[j]=max(dp[j],dp[j-v[i]]+w[i]);
        }
    }
    cout<<dp[m]<<endl;
  return 0;
}

image.gif

多重背包问题1

https://www.acwing.com/problem/content/4/

题目叙述:有N个物品和容量为V的背包。第i种物品最多有s件,每件体积是v,w。求解将哪些物品装入背包,可使得物品体积总和不超过背包容量,且价值总和最大。

思路:dp数组表示前i个物品中选择,承重j时的最大价值。三重循环递推,第一重循环遍历每个物品;第二重循环遍历背包所有承重;第三重循环遍历第i个物品的选择次数,并且选择次数不能超过背包承重;对于任何一个物品都有选k个和不选k个的选择问题。

代码:

#include<bits/stdc++.h>
using namespace std;
const int N=110;
int n,m;
int v[N],w[N],s[N];
int dp[N][N];
int main()
{
  cin.tie(0);
  cout.tie(0);
  ios::sync_with_stdio(0);
    cin>>n>>m;
    for(int i=1;i<=n;i++) cin>>v[i]>>w[i]>>s[i];
    for(int i=1;i<=n;i++){
        for(int j=0;j<=m;j++){
            for(int k=0;k<=s[i] && k*v[i]<=j;k++){
                dp[i][j]=max(dp[i][j],dp[i-1][j-k*v[i]]+k*w[i]);
            }
        }
    }
    cout<<dp[n][m]<<endl;
  return 0;
}

image.gif

多重背包问题2

https://www.acwing.com/problem/content/description/5/

题目叙述:与上题相同,只不过本题数据范围要比上题要大。

思路:多重背包的二进制优化问题。将一个数量为s的物品,通过二进制拆分,转化为若干个只能选一次的物品,从而将多重背包问题转化为01背包问题。对于任何一个整数s都能拆分为若干个2的幂次之和。

代码:

#include<bits/stdc++.h>
using namespace std;
const int N=20200;
int n,m;
int v[N],w[N];
int dp[N];
int main()
{
  cin.tie(0);
  cout.tie(0);
  ios::sync_with_stdio(0);
    cin>>n>>m;
    int cnt=0;
    for(int i=1;i<=n;i++){
        int a,b,s;
        cin>>a>>b>>s;
        int k=1;
        while(k<=s){
            cnt++;
            v[cnt]=k*a;
            w[cnt]=k*b;
            s-=k;
            k*=2;
        }
        if(s>0){
            cnt++;
            v[cnt]=s*a;
            w[cnt]=s*b;
        }
    }
    n=cnt;
    for(int i=1;i<=n;i++){
        for(int j=m;j>=v[i];j--){
            dp[j]=max(dp[j],dp[j-v[i]]+w[i]);
        }
    }
    cout<<dp[m]<<endl;
 
  return 0;
}

image.gif

分组背包问题

https://www.acwing.com/problem/content/9/

问题叙述:有N组物品和容量为V的背包,每组物品有若干个,同一组内的物品最多只能选一个。

每件物品的体积时vij,价值是wij。求解将哪些物品装入背包,可使得物品总体积不超过背包容量,且总价值最大。

思路:dp数组dp[j]表示背包容量为j时,能容纳的最大价值。三重循环递推,第一重遍历每个物品,第二重逆序遍历背包容量,第三重遍历第i组内每个物品k。若当前物品体积不超过背包容量,则更新dp[j]。每个物品k都有选和不选两种方案。

代码:

#include<bits/stdc++.h>
using namespace std;
const int N=110;
int n,m;
int v[N][N],w[N][N];
int s[N];
int dp[N];
int main()
{
  cin.tie(0);
  cout.tie(0);
  ios::sync_with_stdio(0);
    cin>>n>>m;
    for(int i=1;i<=n;i++){
        cin>>s[i];
        for(int j=1;j<=s[i];j++){
            cin>>v[i][j]>>w[i][j];
        }
    }
    for(int i=1;i<=n;i++){
        for(int j=m;j>=0;j--){
            for(int k=1;k<=s[i];k++){
                if(v[i][k]<=j){
                    dp[j]=max(dp[j],dp[j-v[i][k]]+w[i][k]);
                }
            }
        }
    }
    
    cout<<dp[m]<<endl;
  return 0;
}

image.gif

常见DP模板

数字三角形问题

https://www.acwing.com/problem/content/900/

问题叙述:数字三角形,从顶部出发向下走,每步可选择向左下方或右下方移动,一直走到最底层,找出一条路径,使得路径上数字的和最大。

思路:线性DP问题,自定向下递推,每一步两种抉择,注意最后dp[n][i]再取一遍最大值。

代码:

#include<bits/stdc++.h>
using namespace std;
const int N=520;
const int MIN=-1e9;
int graph[N][N];
int dp[N][N];
int n;
int main()
{
  cin.tie(0);
  cout.tie(0);
  ios::sync_with_stdio(0);
    cin>>n;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=i;j++){
            cin>>graph[i][j];
        }
    }
    for(int i=0;i<=n+1;i++){
        for(int j=0;j<=i+1;j++){
            dp[i][j]=MIN;
        }
    }
     dp[1][1]=graph[1][1];
    for(int i=2;i<=n;i++){
        for(int j=1;j<=i;j++){
            dp[i][j]=max(dp[i-1][j-1],dp[i-1][j])+graph[i][j];
        }
    }
    int res=MIN;
    for(int i=1;i<=n;i++) res=max(res,dp[n][i]);
    
    cout<<res<<endl;
  return 0;
}

image.gif

最长上升子序列1

https://www.acwing.com/problem/content/897/

问题叙述:给定一个长度为N的数列,求数值严格单调递增的子序列的长度最大值。

思路:dp数组dp[i]表示第i个元素为结尾的最长上升子序列的长度。第一重循环遍历每个元素,对于每个dp[i]的递推,首先将其初始化为1,之后第二重循环遍历i之前的每个元素,如果之前的元素小于当前元素,则可形成更长的子序列,更新dp[i]为当前的最大值或dp[j]+1。最终在dp数组中更新出最大值。

代码:

#include<bits/stdc++.h>
using namespace std;
const int N=1020;
int n;
int arr[N];
int dp[N];
int main()
{
  cin.tie(0);
  cout.tie(0);
  ios::sync_with_stdio(0);
    cin>>n;
    for(int i=1;i<=n;i++) cin>>arr[i];
    for(int i=1;i<=n;i++){
        dp[i]=1;
        for(int j=1;j<i;j++){
            if(arr[j]<arr[i]) dp[i]=max(dp[i],dp[j]+1);
        }
    }
    int res=-1;
    for(int i=1;i<=n;i++) res=max(res,dp[i]);
    cout<<res<<endl;
  return 0;
}

image.gif

最长上升子序列2

https://www.acwing.com/problem/content/description/898/

题目叙述:题意与上题相同,但数据范围比上题要大的多。

思路:使用数组q维护长度为i的最长上升子序列的末尾元素的最小值,子序列的末尾应尽可能地小,以便给后面地元素留出更大地上升空间。基于二分查找,在数组q中找到最后一个小于arr[i]的元素,设这个元素的索引为r,则arr[i]可以接在长度为r的子序列后面,构成一个长度为r+1的子序列。

代码:

#include<bits/stdc++.h>
using namespace std;
const int N=100010; // 数组最大长度
int arr[N];         // 存储原始序列
// q[i]表示长度为i的递增子序列的最小结尾元素
// 注意:这里q数组的索引从1开始使用,q[0]未使用或可视为无穷小
int q[N];
int n;              // 数组实际长度
int main()
{
    // 关闭输入输出流同步,加速cin/cout
    cin.tie(0);
    cout.tie(0);
    ios::sync_with_stdio(false);
    cin>>n;
    for(int i=1; i<=n; i++) cin>>arr[i];
    int len = 0; // len表示当前找到的最长上升子序列的长度
    // 遍历原始数组中的每个元素
    for(int i=1; i<=n; i++){
        int l = 0, r = len; // 二分查找的范围是 [0, len]
        // 二分查找:寻找最后一个小于 arr[i] 的元素的位置
        // 循环条件 r > l,确保循环会终止
        while(r > l){
            // 计算中间点,+1 是为了避免陷入死循环
            int mid = l + r + 1 >> 1; 
            
            // q[mid] < arr[i] 意味着 arr[i] 可以接在长度为mid的子序列后面
            // 我们尝试寻找一个更长的子序列,所以移动左边界
            if(q[mid] < arr[i]) l = mid;
            // 否则,说明 mid 太大了,移动右边界
            else r = mid - 1;
        }
        
        // 循环结束后,r == l,它指向了最后一个小于 arr[i] 的元素的位置
        // 那么,arr[i]可以构成一个长度为 r+1 的新子序列
        
        // 更新最长长度
        len = max(len, r + 1);
        
        // 将 arr[i] 放置到 q 数组的 r+1 位置
        // 这表示对于长度为 r+1 的子序列,arr[i]是目前已知的最小结尾元素
        q[r+1] = arr[i];
    }
    // 输出最长上升子序列的长度
    cout << len << endl;
    return 0;
}

image.gif

最长公共子序列

https://www.acwing.com/problem/content/899/

题目叙述:给定两个长度分别为N和M的字符串A和B,求既是A的子序列又是B的子序列的字符串长度最长是多少。

思路:dp[i][j]表示A以i结尾,B以j结尾的LCS长度。两重循环遍历A和B的每个元素,如果A的第i个元素等于B的第j个元素,则LCS长度等于其dp[i-1][j-1]+1,当前两个元素不相等,则LCS取max(dp[i-1][j], dp[i][j-1])

代码:

#include<bits/stdc++.h>
using namespace std;
const int N=1020;
int n,m;
char arr[N],brr[N];
int dp[N][N];
int main()
{
  scanf("%d%d",&n,&m);
    scanf("%s%s",arr+1,brr+1);
    
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            if(arr[i]==brr[j]){
                dp[i][j]=max(dp[i][j],dp[i-1][j-1]+1);
            } else {
                dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
            }
        }
    }
    
    cout<<dp[n][m]<<endl;
  return 0;
}

image.gif

最短编辑距离

https://www.acwing.com/problem/content/904/

题目叙述:给定两个字符串A和B,现在要将A经过若干操作变为B,可进行的操作有:

  1. 删除–将字符串 A 中的某个字符删除。
  2. 插入–在字符串 A 的某个位置插入某个字符。
  3. 替换–将字符串 A 中的某个字符替换为另一个字符。

现在请你求出,将 A 变为 B 至少需要进行多少次操作。

思路:dp[i][j]表示将arr[1..i]转换为brr[1..j]所需的最少操作次数,初始化dp数组,全删除或全插入两种初始化思路。两重循环递推,首先考虑插入或删除操作,之后考虑替换操作,替换操作考虑两个字符相等和不等的情况。

代码:

#include<bits/stdc++.h>
using namespace std;
const int N=1010; // 字符串长度的上限
int n,m;          // n: 字符串A的长度, m: 字符串B的长度
char arr[N],brr[N]; // arr存储字符串A, brr存储字符串B (1-based索引)
// dp[i][j]表示将arr[1..i]转换为brr[1..j]所需的最少操作次数
int dp[N][N];
int main()
{
    // 读取输入
    scanf("%d%s",&n,arr+1); // 读取A的长度和A字符串
    scanf("%d%s",&m,brr+1); // 读取B的长度和B字符串
    // 初始化DP表
    // 将A的前i个字符转换为空字符串,需要i次删除
    for(int i=1; i<=n; i++) dp[i][0] = i;
    // 将空字符串转换为B的前j个字符,需要j次插入
    for(int j=1; j<=m; j++) dp[0][j] = j;
    // 填充DP表
    for(int i=1; i<=n; i++){
        for(int j=1; j<=m; j++){
            // 首先,考虑删除和插入操作,取它们的最小值
            // dp[i-1][j]+1 是删除arr[i]的操作
            // dp[i][j-1]+1 是插入brr[j]的操作
            dp[i][j] = min(dp[i-1][j] + 1, dp[i][j-1] + 1);
            // 然后,考虑替换操作
            if(arr[i] == brr[j]){
                // 如果当前字符相等,不需要替换,操作次数就是dp[i-1][j-1]
                dp[i][j] = min(dp[i][j], dp[i-1][j-1]);
            } else {
                // 如果当前字符不相等,需要一次替换操作,操作次数是dp[i-1][j-1] + 1
                dp[i][j] = min(dp[i][j], dp[i-1][j-1] + 1);
            }
        }
    }
    // 输出最终结果
    cout << dp[n][m] << endl;
    return 0;
}

image.gif

编辑距离问题

https://www.acwing.com/problem/content/901/

题目叙述:给定n个字符串和m次询问,每次询问给出一个字符串和一个操作次数上限。对于每次询问,请求出给定的n个字符串中有多少个字符串可以在上限操作次数内经过操作变成询问给出的字符串。

思路:与上一题相同,对目标字符串和询问字符串执行编辑距离操作。

代码:

#include<bits/stdc++.h>
using namespace std;
const int N=1010;
int n,m;
char str[N][15];
int dp[15][15];
int fun(char arr[],char brr[]){
  int la=strlen(arr+1),lb=strlen(brr+1);
  
  for(int i=1;i<=la;i++) dp[i][0]=i;
  for(int j=1;j<=lb;j++) dp[0][j]=j;
  for(int i=1;i<=la;i++){
    for(int j=1;j<=lb;j++){
      dp[i][j]=min(dp[i-1][j]+1,dp[i][j-1]+1);
      if(arr[i]==brr[j]) dp[i][j]=min(dp[i][j],dp[i-1][j-1]);
      else dp[i][j]=min(dp[i][j],dp[i-1][j-1]+1);
    }
  }
  return dp[la][lb];
}
int main()
{
  scanf("%d%d",&n,&m);
  for(int i=1;i<=n;i++){
    scanf("%s",str[i]+1);
  }
  
  for(int i=1;i<=m;i++){
    int res=0;
    char s[15];
    int q;
    scanf("%s%d",s+1,&q);
    for(int j=1;j<=n;j++){
      if(fun(s,str[j])<=q){
        res++;
      }
    }
    cout<<res<<endl;
  }
  return 0;
}

image.gif

合并石子

https://www.acwing.com/problem/content/284/

题目叙述:有N堆石子,编号为1到N,每堆石子有一定的质量,现要将N堆石子合并为一堆。每次只能合并相邻的两堆,合并代价为两堆石子的质量之和,合并后与这两堆石子相邻的石子相邻的石子将和新堆合并,请找出一种合理的方法使得总的代价最小。

思路:区间DP问题。dp[i][j]表示合并第i堆到第j堆石子的最小代价。要将区间[i,j]合并成一堆,最后一步一定是合并两个相邻的区间[i, k] 和 [k+1, j]。为了快速求区间和,对石子重量石子做前缀和。三重循环递推,第一重循环从小到大枚举区间长度,第二重循环枚举区间左端点,第三重循环遍历从左端点到右端点的每一个点,[l,r]区间合并的代价为l到r的区间和加上dp[l][k] + dp[k+1][r]。

代码:

#include<bits/stdc++.h>
using namespace std;
const int N=320;
int n;
int arr[N]; //直接将原始数组转前缀和
int dp[N][N]; //i到j的最小花费
int main()
{
  cin.tie(0);
  cout.tie(0);
  ios::sync_with_stdio(false);
  cin>>n;
  for(int i=1;i<=n;i++) cin>>arr[i];
  for(int i=1;i<=n;i++) arr[i]+=arr[i-1];
  for(int i=2;i<=n;i++){ //枚举长度
    for(int j=1;j+i-1<=n;j++){ //枚举左端点
      int l=j,r=j+i-1;
      dp[l][r]=0x3f3f3f3f;
      for(int k=l;k<r;k++){
        dp[l][r]=min(dp[l][r],dp[l][k]+dp[k+1][r]+arr[r]-arr[l-1]);
      }
    }
  }
  cout<<dp[1][n]<<endl;
  return 0;
}

image.gif

整数划分

https://www.acwing.com/problem/content/902/

题目叙述:一个正整数可以表示为若干个正整数之和,形如:n=n1+n2+…+nk,其中 n1≥n2≥…≥nk,k≥1。现给定正整数n,求n共有多少种不同的划分方法。

思路:计数DP问题。dp[i][j]表示将整数i拆分为j个数的方案数。拆分方案中有两种情况:

  1. 划分中包含 1:去掉一个 1 后,剩下 i-1 需要划分成 j-1 个数,方案数为 dp[i-1][j-1]
  2. 划分中不包含 1:所有数都 ≥2,可以将每个数减 1,相当于将 i-j 划分成 j 个数,方案数为 dp[i-j][j]

双重循环递推,第一重循环枚举总和,第二重循环枚举份数。最终累加dp[n][i]。

代码实现:

#include<bits/stdc++.h>
using namespace std;
const int N=1010;
const int mod=1e9+7;//阶乘量级防止溢出
int n;
int dp[N][N]; //总和为i,总个数为j的方案数 拆分为方案中最小值是1的和不是1两种情况
int main()
{
  cin.tie(0);
  cout.tie(0);
  ios::sync_with_stdio(false);
  cin>>n;
  dp[1][1]=1;
  for(int i=2;i<=n;i++){//和
    for(int j=1;j<=i;j++){
      dp[i][j]=(dp[i-1][j-1]+dp[i-j][j])%mod;
    }
  }
  int res=0;
  for(int i=1;i<=n;i++) res=(res+dp[n][i])%mod;
  cout<<res<<endl;
  return 0;
}

image.gif

没有上司的舞会

https://www.acwing.com/problem/content/287/

题目叙述:有N名职员,编号1到N,其关系为一颗树,父节点就是子节点的直接上司。每个职员有一个快乐指数Hi,先要召开舞会,没有职员愿意与其上司一起参会。再满足这个条件的前提下,主办方希望邀请一部分职员参会,使得所有参会职员快乐指数总和最大,求这个最大值。

思路:树形DP问题。问题转化为在树上选择一些节点,使得没有父子节点同时被选中且被选中的节点权值和最大。对于树上的每个节点 u,定义两种状态:

  • dp[u][0]:不选择节点 u 时,以 u 为根的子树能获得的最大快乐指数
  • dp[u][1]:选择节点 u 时,以 u 为根的子树能获得的最大快乐指数

使用数组模拟邻接表存树。从树的根节点向下深度优先遍历。选择当前节点x时,dp[x][1]初始值为x的快乐指数,遍历x的所有子节点递归搜索,如果选择x,则不能选择其子节点,如果不选择x,则子节点可选也可不选。

代码:

#include<bits/stdc++.h>
using namespace std;
const int N=6020;           // 最大职员数+余量
int n;                      // 职员总数
int h[N], e[N], ne[N], idx; // 邻接表存储树
int happy[N];               // 每个职员的快乐指数
int dp[N][2];               // dp[u][0/1]表示不选/选u时的最大快乐指数
bool flag[N];               // 标记节点是否有父节点
// 添加边a->b
void add(int a, int b){
    e[idx] = b;
    ne[idx] = h[a];
    h[a] = idx++;
}
// 深度优先搜索计算dp值
void dfs(int x){
    dp[x][1] = happy[x];    // 选择x时,初始值为x的快乐指数
    
    // 遍历x的所有子节点
    for(int i = h[x]; i != -1; i = ne[i]){
        int j = e[i];       // 子节点
        dfs(j);             // 递归计算子节点
        
        // 状态转移
        dp[x][1] += dp[j][0];  // 选择x,则不能选择子节点j
        dp[x][0] += max(dp[j][0], dp[j][1]); // 不选择x,子节点可选可不选
    }
}
int main()
{
    // 加速输入输出
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    
    cin>>n;
    for(int i=1; i<=n; i++) cin>>happy[i];
    
    // 初始化邻接表
    memset(h, -1, sizeof(h));
    
    // 构建树
    for(int i=1; i<n; i++){
        int a, b;
        cin>>a>>b;  // b是a的直接上司
        add(b, a);  // 添加边b->a
        flag[a] = true; // 标记a有父节点
    }
    
    // 找到根节点(没有父节点的节点)
    int root = 1;
    while(flag[root]) root++;
    
    // 从根节点开始DFS
    dfs(root);
    
    // 输出结果
    cout<<max(dp[root][0], dp[root][1])<<endl;
    
    return 0;
}

image.gif

滑雪

https://www.acwing.com/problem/content/903/

题目叙述:R行C列的矩阵,表示一个矩形网格滑雪场,第i行第j列的点表示滑雪场第i行第j列区域的高度,一个人从滑雪场中某个区域出发,每次可以向上下左右四个方向滑动一个单位的距离,注意从高向低滑动。求能在滑雪场中完成的最长滑雪轨迹。

思路:可用记忆化搜索优化暴力。dp[i][j]表示从某位置出发能滑行的最远距离。从任意一点x,y出发,深度优先暴力搜索,每次向比自己低的地方下滑,同时使用dp数字缓存记忆化搜过的某位置最远距离。

代码:

#include<bits/stdc++.h>
using namespace std;
const int N=310;
int r,c;
int arr[N][N];
int dp[N][N];//从某位置出发能滑行的最远距离
int dx[4]={0,0,1,-1};
int dy[4]={1,-1,0,0};
int getDP(int x,int y){
  if(dp[x][y]!=-1) return dp[x][y];
  dp[x][y]=1;
  for(int i=0;i<4;i++){
    int a=x+dx[i],b=y+dy[i];
    if(a>=1 && a<=r && b>=1 &&b<=c &&arr[x][y]>arr[a][b]){
      dp[x][y]=max(dp[x][y],getDP(a,b)+1);
    }
  }
  return dp[x][y];
}
int main()
{
  ios::sync_with_stdio(false);
  cin.tie(0);
  cout.tie(0);
  cin>>r>>c;
  for(int i=1;i<=r;i++){
    for(int j=1;j<=c;j++){
      cin>>arr[i][j];
    }
  }
  for(int i=1;i<=r;i++){
    for(int j=1;j<=c;j++){
      dp[i][j]=-1;
    }
  }
  int res=0;
  for(int i=1;i<=r;i++){
    for(int j=1;j<=c;j++){
      res=max(res,getDP(i,j));
    }
  }
  cout<<res<<endl;
  return 0;
}

image.gif


相关文章
|
4月前
|
存储 安全 固态存储
2026阿里云服务器价格表:最新收费标准与38元1年、9.9元1个月、99元1年等活动价格参考
阿里云服务器收费标准涵盖实例配置、带宽及云盘三大核心组件,价格随规格、时长动态调整。2026年活动中推出多类优惠:轻量应用服务器2核4G低至9.9元/月、199元/年;经济型e实例2核2G 3M带宽99元/年;九代ECS(如计算型c9i 8核16G)年付低至6.4折。选购时需注意带宽与CPU/内存的匹配、云盘类型选择及实例适用场景。用户还可领取各种优惠券,在活动价基础上进一步减免,实现成本优化。
1526 4
|
4月前
|
消息中间件 JavaScript 前端开发
详解事件循环与浏览器渲染机制
摘要:浏览器采用多进程架构,渲染主线程通过事件循环机制处理HTML解析、样式计算、布局等任务。异步机制避免主线程阻塞,任务按优先级在微队列、交互队列等不同队列中调度。JS执行会阻碍渲染,因其与渲染任务共享主线程。渲染流程包含解析、样式计算、布局、分层等阶段,最终由合成线程和GPU完成绘制。transform效率高因其仅影响合成阶段,不涉及主线程。reflow是布局重计算,repaint是绘制指令更新,两者均影响性能。
|
4月前
|
消息中间件 Dubbo Java
深入剖析RocketMQ2-实战案例
本文介绍了一个基于电商场景的订单和支付系统实现方案。系统采用SpringBoot+Dubbo+Zookeeper+RocketMQ技术栈,重点解决分布式系统中的数据一致性问题。在订单模块,通过预订单生成、库存扣减、优惠券使用、余额扣减等步骤完成下单流程,并利用RocketMQ实现失败补偿机制。支付模块处理第三方支付回调,通过消息队列异步更新订单状态。系统通过分布式事务保证数据一致性,采用线程池优化消息发送性能,并提供完整的测试方案验证系统功能。本文详细阐述了技术架构、数据库设计、核心业务流程以及异常处理机制
|
4月前
|
消息中间件 存储 Kafka
详解Kafka1-基础使用
Kafka消息队列技术指南 本文系统介绍了Kafka消息队列的核心概念与应用实践。主要内容包括: 消息队列基础 两种模型:生产者-消费者模型和发布-订阅模型 应用场景:异步处理、系统解耦、流量削峰等 Kafka核心架构 重要组件:Broker、Zookeeper、Topic、Partition等 消费者组机制实现负载均衡 消息存储与分区策略 实践操作 集群搭建与环境配置 基准测试方法与性能指标 Java API编程示例(生产者/消费者) 事务编程实现原子操作 高级特性 生产者幂等性原理与配置 事务API与隔
|
4月前
|
JSON 自然语言处理 算法
详解ElasticSearch2-进阶使用
Elasticsearch查询DSL分为叶子查询和复合查询两大类。叶子查询包括全文检索(match/multi_match)、精确查询(term/range)和地理查询等;复合查询则通过bool组合多个查询条件,或使用function_score修改相关性算分。查询结果支持排序、分页(注意深度分页问题)和高亮显示。Java RestClient实现查询时,通过QueryBuilders构建查询条件,SearchRequest组织请求参数,并逐层解析SearchResponse结果。聚合功能包括Bucket分
|
4月前
|
JSON 自然语言处理 数据库
详解ElasticSearch1-基础使用
摘要:本文探讨了数据库模糊搜索的局限性及Elasticsearch(ES)的优势。数据库模糊查询存在性能低、功能单一等问题,而ES通过倒排索引技术实现高效搜索,支持复杂查询需求。文章详细介绍了ES的核心概念、安装部署、索引库操作(CRUD)、文档管理及Java API集成方法,并对比了ES与MySQL的适用场景。最后演示了批量导入文档的实践方案,为海量数据搜索场景提供了专业解决方案。(149字)
|
4月前
|
前端开发 Java Maven
MinIO的预签名直传机制
我们传统使用MinIo做OSS对象存储的应用方式往往都是在后端配置与MinIO的连接和文件上传下载的相关接口,然后我们在前端调用这些接口完成文件的上传下载机制,但是,当并发量过大,频繁访问会对后端的并发往往会对服务器造成极大的压力,大文件传输场景下,服务器被迫承担数据中转的角色,既消耗大量带宽资源,又形成单点性能瓶颈。这时,我们引入了MinIO的一种预签名机制。
MinIO的预签名直传机制
|
7月前
|
人工智能 关系型数据库 分布式数据库
玄晶引擎×阿里云:AI获客全链路落地指南,从RAG建模到私域闭环的云原生实践
玄晶引擎结合阿里云,基于RAG+向量数据库构建企业AI获客底座,实现内容生成、智能分发与私域转化全链路自动化。通过深度集成PolarDB、CDN、函数计算等服务,助力中小企降低获客成本60%以上,提升云资源利用率至85%,3个月落地见效,开发者可直接复用架构与代码。
788 7
|
4月前
|
安全 NoSQL Java
基于JWT+SpringSecurity整合一个单点认证授权机制
本文介绍了基于JWT和SpringSecurity的授权认证机制架构设计。系统采用RBAC权限模型,通过5张表描述用户-角色-权限关系。认证流程包含登录验证、IP检查、密码匹配等环节,使用JWT生成token并保存用户信息到Redis。授权部分利用@PreAuthorize注解和PermissionService实现权限校验,支持单权限、多权限及角色验证。整体架构通过过滤器链实现无状态认证,兼顾安全性和灵活性,为开发者提供了完整的认证授权解决方案。
|
4月前
|
消息中间件 存储 监控
详解Kafka2-进阶机制
本文摘要: 文章详细介绍了Kafka的分区与副本机制,包括生产者分区写入策略(轮询、随机、按key分配、自定义)、消费者组Rebalance机制及其影响,以及消费者分区分配策略(Range、RoundRobin、Sticky)。同时,阐述了Kafka的副本机制、数据存储形式(Segment结构)、消息不丢失机制(生产者ACK、消费者Offset管理)和数据积压问题。此外,还介绍了Kafka的数据清理策略(日志删除与压缩)、配额限速机制(Quotas)及监控工具Kafka-Eagle的使用方法。通过原理分析与