dp 核心最优解 背包:每种方案选不选
n3->n2 有个一max 且选取的时候是单调的时候 可以每次到下一步的时候 多加一个数即可
三维i j k 判断是双体积还是单体+一个限制条件(上一层与无关)决定你的for循环
为什么明明最后统计的时候不需要统计下标为[i][0]的第一列 我们仍然要设置这一列为0 :
我们最后统计的时候,是从f[n][1]开始判断的,这样定义的话方便我们进行状态转移的操作,当i!=0的时候,f[n][i]是从某个f[][0]转移过来的,这显然是一种\(合法操作\),所以我们定义f[][0]=1,最后循环判断的时候去掉这个为零的方案就可以了
永远最关键的一步想清楚 dp[i]代表的是什么 是本层的值,可能是本个数字的最大值的
第二部初始话:讨论可以讨论的
严格从dp[i]的定义来说,dp[0] dp[1] 就不应该初始化,也就是没有意义的数值。
拆分0和拆分1的最大乘积是多少?
这是无解的。
这里我只初始化dp[2] = 1,从dp[i]的定义来说,拆分数字2,得到的最大乘积是1,这个没有任何异议!
为什么需要初始话:状态方程涉及i-1可能会数组越界,应该提前弄好
是不是求最大值的问题 需要max 的函数
多重背包:每个物品的次数不同
分组背包:每组里的背包最多只能选择一个
dp
一.状态表示(i,j的含义)
1.集合是什么(就是i j要到达什么),:
所以选法的集合(你要能包括j方案最大值是多少)
条件:
只从前i个物品选
总条件<=j
2.属性(数组存的是什么):最大值,最小是,方案
一句话dp[i][j]只从前i个物品选,且总体积<=j的选法的集合,s所存的数是这个选法的最大值
j为i个物品拼出体积为j的时候
3.多重背包2:不是无限类 有限个s[i]
通过优化 01背包和多重背包只有一个区别
不优化空间的情况下
rep(i,1,n) rep(j,i,n)
//0 1背包的
01背包: f[i][j]=max(f[i-1][j],f[i-1][j-v]+w) //右边是if(j>=v) //一维的时候 for j是不是顺序逆序 都是 无所谓的
完全背包:f[i][j]=max(f[i-1][j]+f[i][j-v]+w) //把j变成j-v 错位相减得到
一维:(少了if判断 ) 修改两个地方:第二个for循环 +
01背包 for (int i = 1; i <= n; i ++ )
for (int j = m; j >= v[i]; j -- )
f[j] = max(f[j], f[j - v[i]] + w[i]);
//j从[ v[i], m] v[i]是当前物品的体积
完全 for (int i = 1; i <= n; i ++ )
for (int j = v[i]; j <= m; j ++ )
f[j] = max(f[j], f[j - v[i]] + w[i]);//从最大的开始
多重背包求方案类型:
整数划分(多重背包 体积为n 但是i=1-n为可选 属性为方案数)
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1e3+10,mod=1e9+7;
int w[N],v[N],f[N][N];
int main()
{
int n;cin>>n;
for(int i=0;i<=n;i++) f[i][0]=1;//f是数量 , 容量为0时,前 i 个物品全不选也是一种方案 选择方案的时候 不选也是一种方案
for(int i=1;i<=n;i++){
for(int j=1;j<=n;j++){
f[i][j]=f[i-1][j]%mod;
if(j>=i){//能放才放进去
f[i][j]=(f[i-1][j] +f[i][j-i])%mod;//注意是方案数 所以是加法 不加现在这个数集合 和加这个数的集合数量
}
}
}
cout<<f[n][n];
return 0;
}
二维费用背包问题:双体积
f[i][j][k]=只从前i个物品中选择 且体积1不超过j 体积2花费不超过k的选法的最大价值
二维费用的背包问题https://www.acwing.com/problem/content/description/8/
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 110;
int n, v, m;
int f[N][N];
int main() {
cin >> n >> v >> m;
for (int i = 1; i <= n; i ++ ) {
int a, b, w;
cin >> a >> b >> w;
for (int j = v; j >= a; j -- ) {
for (int k = m; k >= b; k -- ) {
f[j][k] = max(f[j][k], f[j - a][k - b] + w);
}
}
}
cout << f[v][m] << endl;
return 0;
}
##多重背包 物品的选择且有限制 01背包的升级
for (int i = 1; i <= n; i ++ )
for (int j = 0; j <= m; j ++ )//j从0开始
for (int k = 0; k <= s[i] && k * v[i] <= j; k ++ )//k表示选择j件物品 注意当k大于某个值的时候就会停止
f[i][j] = max(f[i][j], f[i - 1][j - v[i] * k] + w[i] * k);
//一维
//j从后往前 否则会用本次更新的东西继续更新
for(int i=0;i<n;i++)
for (int j = m; j >= 0; j -- ){
for (int k = 1; k <= c&&ka<=j ; k ++ ){//0可以省略
f[j]=max(f[j],f[j-ka]+b*k);
}
}
多重背包2:二进制优化优化后等价于01背包
a:12个武平 b:7个物品 c:2个物品
v[cnt]=1a 2a 4a 4a 1b 2b 3b 1b 1c 1c
w[cnt]=1a 2a 4a 4a 1b 2b 3b 1b 1c 1c
cnt=10
于是变成了 该不该选择 这cnt个0 1物品的每个的问题
include
using namespace std;
const int N = 12010, M = 2010;
int n, m;
int v[N], w[N]; //逐一枚举最大是N*logS
int f[M]; // 体积<M
int main()
{
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)//s是总共几个可以选
{
cnt ++ ; //组别先增加 是下标
v[cnt] = a * k ; //整体体积
w[cnt] = b * k; // 整体价值
s -= k; // s要减小
k = 2; // 组别里的个数增加
}
//剩余的一组
if(s>0)
{
cnt ++ ;
v[cnt] = as;
w[cnt] = b*s;
}
}
n = cnt ; //枚举次数正式由个数变成组别数
//01背包一维优化
for(int i = 1;i <= n ;i ++)
for(int j = m ;j >= v[i];j --)
f[j] = max(f[j],f[j-v[i]] + w[i]);
cout << f[m] << endl;
return 0;
}
输入
4 5
1 2 3
2 4 1
3 4 3
4 5 2
for(int i=1;i<=n;i++) cout << v[i]<<" ";
cout << endl;
for(int i=1;i<=n;i++) cout << w[i]<<" ";
cout << endl;
输出
1 2 2 3 6 4 4
2 4 4 4 8 5 5
可以发现把A3件 B1件 C3件 D2件分成
=(1 2) (1) (1 2) (1 1) 这么多件的01取不取的问题(将哪些物品放入背包使得物品总体积包超过背包容量且总价值最大 由01背包保证最优解)
##庆功会https://www.acwing.com/activity/content/code/content/117240/
分组背包裸题
include
include
include
using namespace std;
const int N = 6050;
int q[N],f[N];
int main()
{
int n,m;cin>>n>>m;
for (int i = 1; i <= n; i ++ ){
int v,w,s;scanf("%d%d%d",&v,&w,&s);
for (int j = m; j >=0; j -- )//01背包枚举体积
for (int k = 0; k <= s&& kv<=j; k ++ ){//这个不依赖于其他状态 从小到大 从大到小都可以
f[j]=max(f[j],f[j-vk]+w*k);
}
}
cout << f[m];
return 0;
}
分组背包
i j k 先小到大枚举每个组 然后大到小是体积 然后小到大是选这组的哪个
include
include
include
using namespace std;
const int N = 105;
int f[N],w[N][N],v[N][N];
int s[N];
int main()
{
int n,m;
cin >>n>>m;
for (int i = 1; i <= n; i ++ ){
cin >> s[i];
for (int j = 1; j <= s[i]; j ++ ){
int a,b;
cin >> a>>b;
w[i][j]=b,v[i][j]=a;
}
}
for (int i = 1; i <= n; i ++ ){//先枚举组
for (int j = m; j >= 0; j -- ){//体积
for (int k = 1; k <= s[i]; k ++ )//每个组的个数
if(j>=v[i][k])
f[j]=max(f[j],f[j-v[i][k]]+w[i][k]);
}
}
cout << f[m];
return 0;
}
宠物小精灵
求最大值
需要留一滴血 所以第二维 只能到了m-1;
因为需要f[i][j][k]表示前i个精灵 消耗的精灵球少于等于j 消耗的血量小于等于k的(但是k只会到m-1) 收获到的精灵最大值
f[j][k] = max(f[j][k], f[j - w[i]][k - v[i]] + 1);
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1005;
int f[N][N];
int main()
{
int n,m,v;
cin >> v>>m>>n;
for (int i = 0; i < n; i ++ ){
int a,b,c;
cin >> a>>b;
for (int j =v ; j>= a; j -- ){
for (int k = m-1; k >= b; k -- ){
f[j][k]=max(f[j][k],f[j-a][k-b]+1);
}
}
}
int res=m-1;
cout << f[v][m-1]<<" ";
for (int i = 0; i < m-1; i ++ ){//从前到后找到最小等于f[v][m-1]的第二维
if(f[v][i]==f[v][m-1]) {
res=i ;
break;
}
}
cout << m-res;
return 0;
}
数字组合
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 105,M=1e4+10;
int a[N];
int f[M];
int main()
{
int n,m;
cin>>n>>m;
for (int i = 0; i < n; i ++ ){
cin >> a[i];
}
f[0]=1;//组成0的一开始为1 这样每个选择a[i]的方案都可以加上1
for (int i = 0; i < n; i ++ ){
for (int j = m; j >= a[i]; j -- ){
f[j]=f[j]+f[j-a[i]];//组成j的方案数 由继承不选i的方案 和 继承选择i的方案继承而来
}
}
// for (int i = 1; i <= m; i ++ ) {
// cout << f[i];
// }
cout << f[m];
return 0;
}
货币系统 某种面值的货币 组成 m元
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 3005;
long long f[N];
int main()
{
int n,m;cin>>n>>m;
f[0]=1;//组成为0价值 为0保证 组成为其他的数能够成功进行
for (int i = 0; i < n; i ++ ){
int x;cin>>x;
for (int j = x; j <= m; j ++ ){
f[j]=f[j]+f[j-x];
}
}
cout << f[m];
return 0;
}
0/1背包
变种
链接:https://ac.nowcoder.com/acm/contest/23479/I
来源:牛客网
链接:https://ac.nowcoder.com/acm/contest/23479/I
来源:牛客网
小红正在研究如何把符卡组合出尽可能大威力的组合魔法。
小红共有 nn 种符卡可以选择,每种符卡最多只能选择一次,每个符卡的魔力消耗为 a,威力为 b 如果将多个符卡进行组合,则可以发动一个组合魔法。组合魔法的魔力消耗为选择的符卡的魔力消耗的总和,其威力为选择的符卡的威力的总和。
小红必须保证最终符卡的魔力消耗总和为 k的倍数,否则小红将受到魔力反噬而发动魔法失败。
小红想知道,自己能发动的组合魔法最大的威力是多少?
2 3
1 2
2 1
输出
3
说明
两个符卡都选上,融合出的魔法消耗为3,是3的倍数。威力是3。
0。1背包的变种还有对负数取模需要先转为绝对值小于 k 的负数,然后加 k 再模 k 。
#include<bits/stdc++.h>
using namespace std;
#define ll long long
ll a[1010][2],dp[1010][1010];
int main(){
int n,k,i,j;
cin>>n>>k;
//dp代表前 i 个物品重量为 j 的最大值
for(i=1;i<=n;i++)cin>>a[i][0]>>a[i][1];//输入数据
for(i=0;i<=n;i++)for(j=0;j<=k;j++)dp[i][j]=-1e16;//对于前i个物品 不是k的倍数的话是不符合题意的
//因为求的是最大值,所以注意这里的初始化一定要尽可能小!代表是取不到的。
dp[0][0]=0;
//前0件物品,显然取到的最大威力为0。此时消耗魔力模p等于0
for(i=1;i<=n;i++){
for(j=0;j<k;j++){
dp[i][j]=dp[i-1][j];//不取
//这一部分是不取第i个符卡。
dp[i][j]=max(dp[i][j],dp[i-1][(j-a[i][0]%k+k)%k]+a[i][1]);//注意dp方程左边永远是i和j,右边则是小一点的数,这里j-w表示上面的需要腾出这么多空间
//这一部分是取第i个符卡,那么目前的威力如果模k为j的话,一定是从“前i-1个符卡,威力模k为j-a[i]转移而来”
//这里位置就是最特殊的地方 因为魔力值必须为倍数 所以减的时候一定会减出负数 为了 不让越界 要对减的数(即容易倍数的体力值)取模倍数k ,减后再加上倍数k, 在将前面一大坨取模
}
}
if(dp[n][0]<=0)cout<<-1;//第n个物品如果还是负数的话就或说明
else cout<<dp[n][0];//在选了第n个物品,输出重量为0的价值
}
#include<iostream>
using namespace std;
#define int long long
int a[10005];
int b[10005];
int dp[1005][1005];
signed main()
{
int n,k;cin>>n>>k;
for(int i=1;i<=n;i++)cin>>a[i]>>b[i];
for(int i=1;i<=n;i++)
{
for(int j=0;j<k;j++)
{
dp[i][j]=dp[i-1][j];
}
for(int j=0;j<k;j++)
{
if((dp[i-1][j]==0)&&j!=0){continue;}
dp[i][(j+a[i])%k]=max(dp[i][(j+a[i])%k],b[i]+dp[i-1][j]);//此处把j放在后面,使用取模不需要这么复杂
//这里j表示 物体重量的大小 因为要求的是 刚好即为倍数 所以余k为0 就是答案
}//cout<<dp[i][0]<<" "<<dp[i][1]<<" "<<dp[i][2]<<" "<<dp[i][3]<<endl;
}
if(dp[n][0]==0)cout<<-1;
else{cout<<dp[n][0];}
}
当输出日志 就会发现 我们输出的答案并不是平时需要的最大值3 这正是这个刚好整除条件给我们最大的不同 我们只需要比较 j=0这一列的最大值即可
0 2 0 0
1 3 0 0
1
分组背包
例:链接:https://ac.nowcoder.com/acm/problem/14602
来源:牛客网
已知阴阳师有N个模式可以操作,模式i有ai种操作,但每种模式每日只能选用一种操作,可以不选。操作j能收益vj,但需要花费体力wj点。xinjun每日拥有体力M点,求他每日最多能得到多少收益。
很明显一天内多个模式的选取单个操作 而这些模式共用一个体力值 属于分组背包问题
for 所有的组
----for k=体力 k>0
-------- for 这个组的操作
------------ dp递推式
for(int i=1;i<=n;i++){//模式
for(int k=m;k>=0;k--){0 1的空间压缩 少一个维度
for(int j=1;j<=a[i];j++){//操作
if(k>=w[i][j]) dp[k]=max(dp[k],dp[k-w[i][j]]+v[i][j]);
}
}
}
蓝桥杯砝码称重https://www.acwing.com/problem/content/submission/3420/
背包求和问题 列为和的最大值
#include <bits/stdc++.h>
using namespace std;
const int N = 110, M = 2e5 + 10;//这里开数据范围的两倍
int sum;
int n;
int w[N];
bool f[N][M];
int main() {
cin>>n;
for (int i = 1; i <= n; i++)
{
scanf("%d", &w[i]);
sum+=w[i];//sum是最大的方案值 用来作为下标
}
f[0][0]=true;
for (int i = 1; i <= n;i++)//i表示第i个砝码
for (int j = 0; j <=sum;j++)//
f[i][j]=f[i-1][j]||f[i-1][j+w[i]]||f[i-1][abs(j-w[i])];//abs是因为这是砝码 反过来操作就变成正的了
//只要有一个非空,f[i][j]就非空
int ans = 0;
for (int i = 1; i <=sum;i++)
if(f[n][i])ans++;//不为零说明可以选出这个质量的砝码
//j下标表示的是能不能称到这个种类
for (int i = 1; i <= n;i++){
for (int j = 0; j <=sum;j++) cout << f[i][j];
cout << endl;
}
cout << ans;
return 0;
}
//输入
3
1 4 6
//输出
110000000000 第一个物品变化而来
110111000000
111111110111
选取数对 https://www.acwing.com/problem/content/4381/
选取 k个长度为m 的区间
需要这些区间里面的总和最大
关键词 前缀和 + dp 最后的条件为区间
dp[i][j] 表示最后一个区间右端点为i 选取了j个区间 总和的 最大值
维度 右端点 区间
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
typedef long long LL;
const int N = 5010;
int n, m, k;
LL s[N], f[N][N];
int main()
{
scanf("%d%d%d", &n, &m, &k);
for (int i = 1; i <= n; i ++ )
{
int x;
scanf("%d", &x);
s[i] = s[i - 1] + x;//前缀和
}
memset(f, -0x3f, sizeof f);//可能最大值为0 设置为负无穷
for (int i = 0; i <= n; i ++ ) f[i][0] = 0;//如果一个区间都不选的话就是0
for (int j = 1; j <= k; j ++ )//一个一个区间的搞
{
LL maxf = 0;//设置初始值
for (int i = j * m; i <= n; i ++ )//i是右端点 从j*m开始枚举
{
maxf = max(maxf, f[i - m][j - 1] + s[i] - s[i - m]);
f[i][j] = maxf;
}
}
LL res = 0;
for (int i = 1; i <= n; i ++ )
res = max(res, f[i][k]);
printf("%lld\n", res);
return 0;
}
位数问题https://www.acwing.com/problem/content/1236/
从n个数里面找3个数 并且要求这3个数的总和 是K的倍数
三维背包+倍数取模
易于理解的三维 i,j,k 所有从前i个数里面选 而且已经选择了j个数 且总和%K==k的集合
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <vector>
using namespace std;
const int N = 1e5 + 5;
int n, K, a[N];
int main(void){
cin >> n >> K;
for (int i = 1; i <= n; i++) scanf("%d", &a[i]);
// vector<vector<vector<int>>> dp(n + 1, vector<vector<int>> (4, vector<int> (K, -2e9)));
dp[n+1][4][N]=-inf;
memset( dp,0x3f,sizeof dp);
for (int i = 0; i <= n; i++) dp[i][0][0] = 0;
for (int i = 1; i <= n; i++){//朴实无华的三重循环
for (int j = 1; j <= 3; j++){
for (int k = 0; k < K; k++){//每一个选择的操作 由 (k-a[i])%k 而来
dp[i][j][k] = max(dp[i - 1][j][k], dp[i - 1][j - 1][((k - a[i]) % K + K) % K] + a[i]);
}
}
// printf("%d %d %d\n", dp[i][1][0], dp[i][2][0], dp[i][3][0]);
}
cout << dp[n][3][0];
return 0;
}
优化只取最大值
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
using namespace std;
const int N = 1010;
int n, m;
vector<int> a[N];
int f[4][N];
int main()
{
scanf("%d%d", &n, &m);
for (int i = 0; i < n; i ++ )
{
int x;
scanf("%d", &x);
a[x % m].push_back(x);//放进对应的位置
}
memset(f, -0x3f, sizeof f);
f[0][0] = 0;//只有这个是有效的 表示一个数都不选并且余数为0
for (int i = 0; i < m; i ++ )//将n个数转化为m类数
{
sort(a[i].begin(), a[i].end());
reverse(a[i].begin(), a[i].end());//每个a[i]从大到小排序
for (int u = 0; u < 3 && u < a[i].size(); u ++ )//枚举第m类前3大的数
{
int x = a[i][u];//a[i]里面的每个数表示余数为i的数
//下面是对这个的每一维状态 都看看这个x是否满足
for (int j = 3; j >= 1; j -- )//j相当于是体积 需要依赖上一层 也就是f[i][j]=f[i-1][j]
for (int k = 0; k < m; k ++ )//k从小到大从大到小的无所谓 不依赖别的状态 f[][][k]=f[][][k-x]
f[j][k] = max(f[j][k], f[j - 1][(k - x % m + m) % m] + x);//从选择了j-1个数过来的
}
}
printf("%d\n", f[3][0]);//选了3个数
return 0;
}
线段问题
摘花生https://www.acwing.com/problem/content/1017/
线性dp
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
int t;
const int N = 105;
int f[N][N],w[N][N];//i j表示走到第i行第j列的时候 具有最多的数字 由f左边或f上边的最大值 决定
int main()
{
cin >> t;
while(t--){
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;
}
return 0;
}
输入
2
2 2
1 1
3 4
2 3
2 3 4
1 6 5
输出
8
1 2
4 8
16
2 5 9
3 11 16
数字三角形https://www.acwing.com/problem/content/900/
新增边界情况 考虑为负数时候
解法1从下往上走 更快
#include <iostream>
#include <cstring>
#include <algorithm>
#include <limits>
using namespace std;
const int N = 505;
int g[N][N],f[N][N];
int main()
{
int n;cin>>n;
for (int i = 1; i <= n; i ++ ){
for (int j= 1; j <= i; j ++ ){
cin >> g[i][j];
}
}
f[1][1]=g[1][1];
for(int i=2;i<=n;i++){
for(int j=1;j<=i;j++){
if(j==1)
f[i][j]=f[i-1][j]+g[i][j];
else if(j==i)
f[i][j]=f[i-1][j-1]+g[i][j];
else
f[i][j]=max(f[i-1][j-1]+g[i][j],f[i-1][j]+g[i][j]);
}
}
int res=f[n][1];
for(int i=0;i<=n;i++){
for(int j=0;j<=n;j++){
cout<<f[i][j]<<" ";
}
cout<<endl;
}
for(int i=1;i<=n;i++) res=max(res,f[n][i]);
cout<<res;
return 0;
}
输入
2
-1
-1 -2
输出
0 0 0
0 -1 0
0 -2 -3
最长上升子序列
线性dp
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1010;
int n;
int a[N];
int f[N];
int main()
{
cin >> n ;
for (int i = 1; i <= n; i ++ ) cin >> a[i];
int res = 0;
for (int i = 1; i <= n; i ++ )
{
f[i] = 1;//初始化,f[i]表示从第一个数字开始算,以w[i]结尾的最大的上升序列。一开始f[i]为1 就是自己本身
for (int j = 1; j < i; j ++ )
if (a[i] > a[j])//合法的情况才这么算
f[i] = max(f[i], f[j] + 1);//f[j]就是最优解 因为j是最长的 所以是j是一个很小的下标 依然会有更大的下标j覆盖掉她的情况 所以是最优的
// 最长的在中间
res = max(res, f[i]);
}
cout << res << endl;
return 0;
}
输入
10
-5 -7 -2 1 -9 -7 4 5 6 1
输出(注意到了-7的时候 最长为2 但是到了4的时候 最长为4 这是满足子序列定义 只要相对顺序不变就可以 可以任意删改元素 不一定连续)
1 1 2 3 1 2 4 5 6 3
6
记忆化搜索
滑雪https://www.acwing.com/problem/content/903/
i j就是四个方向最大值+1 就是本身的最大值
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 310;
int n, m;
int g[N][N];
int f[N][N];
int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};
int dp(int x, int y)
{
int &v = f[x][y];
if (v != -1) return v;//这里递归会一直尽可能的递归到高度最小值 那个时候的最小值才是真正的
v = 1;//初始化 设为1
for (int i = 0; i < 4; i ++ )
{
int a = x + dx[i], b = y + dy[i];
if (a >= 1 && a <= n && b >= 1 && b <= m && g[x][y] > g[a][b])
v = max(v, dp(a, b) + 1);//换了一个起点 那么依然+1 一边dp 一般dfs 最后获得最大值 先递归最大值
}
return v;
}
int main()
{
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= m; j ++ )
scanf("%d", &g[i][j]);
memset(f, -1, sizeof f);
int res = 0;
for (int i = 1; i <= n; i ++ )
for (int j = 1; j <= m; j ++ )//每一个点开始
res = max(res, dp(i, j));
printf("%d\n", res);
return 0;
}