[算法]背包DP(01背包、完全背包问题、多重背包、分组背包、混合背包问题、有依赖的背包问题等)

DP分析方法

DP就是在有限集中找出最值的问题, 所以先定义出集合, 再推出状态转换方程式, 那么就可以计算出全部的集合的值。

流程:

  • 状态表示 : 分为状态描述和集合的属性, 通过这两个信息得出集合的定义。
    • 状态描述 : 分析解决问题所需要的所有可变条件, 根据可变条件的数量来定义出对应数量的维度, 每个维度都表示出一个可变条件, 而表示的值就是这个集合的最优解, 比如有二维, 那么就是 f ( i , j ) f(i,j) f(i,j), 而 f ( i , j ) f(i,j) f(i,j)表示的值就是最优解
      • 维度越小越好, 因为维数越多那么就需要计算量越大, 对应的时间复杂度会上升。
    • 集合属性: 根据题目看集合的目的是什么, 通常有最大值、最小值、数量。
    • 集合定义: 根据集合的维度和集合属性, 来定义出集合之间每个信息的关系。
  • 状态计算
    • 要求 :
      • 不重复: 求数量时必须满足, 但是判断是否存在时可以不满足, 因为哪怕多次推导出状态的存在性, 那也不会导致结果的错误。
      • 不遗漏: 任何情况都必须满足。
    • 状态更新 : 划分状态, 求出状态转换方程式。
      • 相当于一个状态机, 推导出各个集合的关系, 从而得出状态转换方程式。

时间复杂度: 状态数量 * 状态计算所需的时间

: 这方法从acwing学习到的。


01背包

01背包: 每个物品只能用一件,也就是只能选择0或者1。

状态表示:

  • 状态描述: 01背包总共有三个信息, 分别是物品数、容量、总价值, 其中总价值是需要求出来的信息, 因此只需要有两个维度。
  • 集合属性: 最大值
  • 集合定义 : f ( i , j ) f(i,j) f(i,j), 表示只从前 i 个物品中选择,且总体积不超过 j 的所有选法中,最大的总价值。
    状态计算 : 对于集合 f ( i , j ) f(i,j) f(i,j)
  • 不选第 i 个物品 : f ( i , j ) = f ( i − 1 , j ) f(i,j)=f(i-1,j) f(i,j)=f(i−1,j)
  • 选第 i 个物品 : f ( i , j ) = f ( i − 1 , j − v i ) + w i f(i,j)=f(i-1,j-v_i)+w_i f(i,j)=f(i−1,j−vi)+wi, 其中 j ≥ v i j \ge v_i j≥vi
  • 状态转换方程式 : f ( i , j ) = max ⁡ { f ( i − 1 , j ) , f ( i − 1 , j − v i ) + w i } f(i,j)=\max\{f(i-1,j), f(i-1,j-v_i)+w_i\} f(i,j)=max{f(i−1,j),f(i−1,j−vi)+wi}
cpp 复制代码
/*
定义一个二维数组去存储所有的情况

有两种情况
1)不选第i个物品,f[i][j]=f[i-1][j];
2)选第i个物品,f[i][j]=f[i-1][j-v[i]]+w[i]; 

f[i][j]=max(第一种情况,第二种情况) 
*/

#include <iostream>
using namespace std;

const int N = 1010;
int f[N][N];
int w[N], v[N];
int n, m;

int main()
{
	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 = 0; j <= m; j++)
		{
			f[i][j] = f[i - 1][j];
			if (j >= v[i]) f[i][j] = max(f[i][j], f[i - 1][j - v[i]] + w[i]);
		}

	cout << f[n][m] << endl;
	return 0;
}

DP问题的优化都是对代码进行等价变形

优化是针对空间来进行优化的

每一次更新的值都是把i-1次的值变成i次的值

对于 f [ i ] [ j ] = f [ i − 1 ] [ j ] f[i][j]=f[i-1][j] f[i][j]=f[i−1][j]去掉 i 维, 变成 f [ j ] = f [ j ] f[j]=f[j] f[j]=f[j], 这个和一开始的代码是否等价?

是等价的, 因为第 i 次循环更新 j 时, f [ j ] f[j] f[j]中存储的本来就是第 i − 1 i-1 i−1次循环的j值。

f [ i ] [ j ] = m a x ( f [ i ] [ j ] , f [ i − 1 ] [ j − v [ i ] ] + w [ i ] ) f[i][j]=max(f[i][j],f[i-1][j-v[i]]+w[i]) f[i][j]=max(f[i][j],f[i−1][j−v[i]]+w[i])去掉 i 维, 变成 f [ j ] = m a x ( f [ j ] , f [ j − v [ i ] ] + w [ i ] ) f[j]=max(f[j],f[j-v[i]]+w[i]) f[j]=max(f[j],f[j−v[i]]+w[i])
f [ j ] f[j] f[j]上面已经解释过, 对于 f [ j − v [ i ] ] f[j-v[i]] f[j−v[i]], 如果是从0到m遍历, f [ j − v [ i ] ] f[j-v[i]] f[j−v[i]] 会先于 f [ j ] f[j] f[j] 被更新。此时计算 f [ j ] f[j] f[j] 用到的 f [ j − v [ i ] ] f[j-v[i]] f[j−v[i]] 已经是第 i 轮更新过的值, 这等价于 f [ i ] [ j − v [ i ] ] f[i][j-v[i]] f[i][j−v[i]],而不是我们需要的 f [ i − 1 ] [ j − v [ i ] ] f[i-1][j-v[i]] f[i−1][j−v[i]]。但如果从m到0遍历, f [ j ] f[j] f[j] 会先于 f [ j − v [ i ] ] f[j-v[i]] f[j−v[i]] 被计算。此时计算 f [ j ] f[j] f[j] 用到的 f [ j − v [ i ] ] f[j-v[i]] f[j−v[i]] 仍然是第 i − 1 i-1 i−1 轮的值, 等价于 f [ i − 1 ] [ j − v [ i ] ] f[i-1][j-v[i]] f[i−1][j−v[i]],逻辑正确。

所以最后就变成了 f [ j ] = m a x ( f [ j ] , f [ j − v [ i ] ] + w [ i ] ) f[j] = max(f[j], f[j - v[i]] + w[i]) f[j]=max(f[j],f[j−v[i]]+w[i]); ( j 逆序遍历)

优化之后的代码

cpp 复制代码
#include <iostream>
using namespace std;

const int N = 1010;
int f[N];
int w[N], v[N];
int n, m;

int main()
{
	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--)
			f[j] = max(f[j], f[j - v[i]] + w[i]);

	cout << f[m] << endl;
	return 0;
}

完全背包问题

完全背包问题: 每个物品可以用无限次, 而01背包问题每个物品只能用一次。

状态表示:

  • 状态描述: 总共有四个信息, 分别是物品数、容量、总价值、次数, 这里次数不需要记录(原因看状态计算那里), 其中总价值是需要求出来的信息, 因此需要有两个维度。
  • 集合属性: 最大值
  • 集合定义 : f ( i , j ) f(i,j) f(i,j), 表示只从前 i 个物品中选择,且总体积不超过 j 的所有选法中,总价值最大的选法。
    状态计算 : 对于集合 f ( i , j ) f(i,j) f(i,j)
  • 选第 i 个物品 0 次 : f ( i , j ) = f ( i − 1 , j ) f(i,j)=f(i-1,j) f(i,j)=f(i−1,j)
  • 选第 i 个物品 1 次 : f ( i , j ) = f ( i − 1 , j − v i ) + w i f(i,j)=f(i-1,j-v_i)+w_i f(i,j)=f(i−1,j−vi)+wi
  • ...
  • 同理可得选第 i 个物品 k 次 : f ( i , j ) = f ( i − 1 , j − k ⋅ v i ) + k ⋅ w i f(i,j)=f(i-1,j-k \cdot v_i)+k \cdot w_i f(i,j)=f(i−1,j−k⋅vi)+k⋅wi
  • 状态转换方程式 :
    • f ( i , j ) = max ⁡ { f ( i − 1 , j ) , f ( i − 1 , j − v i ) + w i , f ( i − 1 , j − 2 v i ) + 2 w i , ...   } f(i, j) = \max\{f(i-1, j), f(i-1, j-v_i)+w_i, f(i-1, j-2v_i)+2w_i, \dots \} f(i,j)=max{f(i−1,j),f(i−1,j−vi)+wi,f(i−1,j−2vi)+2wi,...}
    • 同理可得, f ( i , j − v i ) = max ⁡ { f ( i − 1 , j − v i ) , f ( i − 1 , j − 2 v i ) + w i , ...   } f(i, j-v_i) = \max\{f(i-1, j-v_i), f(i-1, j-2v_i)+w_i, \dots \} f(i,j−vi)=max{f(i−1,j−vi),f(i−1,j−2vi)+wi,...}
    • 两式对比可得: f ( i , j ) = max ⁡ { f ( i − 1 , j ) , f ( i , j − v i ) + w i } f(i,j)=\max\{f(i-1,j), f(i,j-v_i)+w_i\} f(i,j)=max{f(i−1,j),f(i,j−vi)+wi}

朴素做法

cpp 复制代码
#include <iostream>
using namespace std;

const int N = 1010;
int f[N][N];
int v[N], w[N];
int n, m;

int main()
{
	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 = 0; j <= m; j++)
		{
			f[i][j] = f[i - 1][j];
			if (j >= v[i])
				f[i][j] = max(f[i][j], f[i][j - v[i]]+w[i]);
		}
	cout << f[n][m] << endl;
	return 0;
}

优化

对于 f [ i ] [ j ] = f [ i − 1 ] [ j ] f[i][j]=f[i-1][j] f[i][j]=f[i−1][j] 去掉 i 维变成 f [ j ] = f [ j ] f[j]=f[j] f[j]=f[j] 是否等价?

不等价, 在完全背包的优化中, 我们依赖的是 f [ j − v [ i ] ] f[j-v[i]] f[j−v[i]] 已经被第 i 轮更新过, 所以不能简单地认为 f [ j ] = f [ j ] f[j]=f[j] f[j]=f[j] 等价。

对于 f [ i ] [ j ] = m a x ( f [ i − 1 ] [ j ] , f [ i ] [ j − v [ i ] ] + w [ i ] ) f[i][j]=max(f[i-1][j], f[i][j-v[i]]+w[i]) f[i][j]=max(f[i−1][j],f[i][j−v[i]]+w[i]) 去掉 i 维变成 f [ j ] = m a x ( f [ j ] , f [ j − v [ i ] ] + w [ i ] ) f[j]=max(f[j], f[j-v[i]]+w[i]) f[j]=max(f[j],f[j−v[i]]+w[i]) 是否等价?

如果从m到0遍历, f [ j ] f[j] f[j]先于 f [ j − v [ i ] ] f[j-v[i]] f[j−v[i]]计算,此时 f [ j − v [ i ] ] f[j-v[i]] f[j−v[i]] 还是第 i − 1 i-1 i−1 轮的值,等价于 f [ i − 1 ] [ j − v [ i ] ] f[i-1][j-v[i]] f[i−1][j−v[i]]。这变成了01背包的逻辑,是错误的。

如果从0到m遍历, f [ j − v [ i ] ] f[j-v[i]] f[j−v[i]]先于 f [ j ] f[j] f[j]计算,此时 f [ j − v [ i ] ] f[j-v[i]] f[j−v[i]] 已经是第 i 轮更新过的值,等价于 f [ i ] [ j − v [ i ] ] f[i][j-v[i]] f[i][j−v[i]]。这与优化后的方程 f [ i ] [ j ] = m a x ( f ( i − 1 , j ) , f ( i , j − v i ) + w i ) f[i][j] = max(f(i-1,j), f(i,j-v_i)+w_i) f[i][j]=max(f(i−1,j),f(i,j−vi)+wi) 逻辑一致。

代码

cpp 复制代码
#include <iostream>
using namespace std;

const int N = 1010;
int f[N];
int n, m;
int w[N], v[N];

int main()
{
	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++)
			f[j] = max(f[j], f[j - v[i]] + w[i]);

	cout << f[m] << endl;
	return 0;
}

多重背包

多重背包: 每个物品有多件。

可以把它看作是几个01背包问题, 有多个物品, 分别是 ( 1 ⋅ v , 1 ⋅ w ) , ( 2 ⋅ v , 2 ⋅ w ) , ... , ( k ⋅ v , k ⋅ w ) (1 \cdot v,1 \cdot w), (2 \cdot v,2 \cdot w), \dots, (k \cdot v,k \cdot w) (1⋅v,1⋅w),(2⋅v,2⋅w),...,(k⋅v,k⋅w)。

cpp 复制代码
#include <iostream>
using namespace std;

const int N = 110;
int w[N], v[N], s[N];
int f[N][N];
int n, m;

int main()
{
	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++) {
            f[i][j] = f[i-1][j]; 
			for (int k = 1; k <= s[i]; k++) 
				if(k*v[i]<=j)
					f[i][j] = max(f[i][j], f[i - 1][j - k * v[i]] + k * w[i]);
        }
	cout << f[n][m] << endl;
	return 0;
}

优化 :

把多重背包问题拆成01背包问题。
O ( n ⋅ m ⋅ log ⁡ s ) O(n \cdot m \cdot \log s) O(n⋅m⋅logs)

比如7拆成1,2,4 , 这三个数就能表达出0~7中的数。

比如10拆成1,2,4,3 而不是1,2,4,8 ,因为如果拆成1,2,4,8这四个数能表示出115中的数,但我们只想要表示出110的数,那么最后的3是怎么求出来的呢,知道1,2,4之后如果再加8就会比10大了,所以我们把10-1-2-4最后得出3。

cpp 复制代码
#include <iostream>
#include <vector>
using namespace std;

const int N = 2010; 
int n, m;
int f[N];

struct Good 
{
	int w, v;
};

int main()
{
	cin >> n >> m;
	vector<Good> goods;
	for (int i = 0; i < n; i++)
	{
		int v, w, s;
		cin >> v >> w >> s;
		for (int j = 1; j <= s; j *= 2)
		{
			s -= j;
			goods.push_back({ j * w, j * v });//每个物品乘以它对应的个数
		}
		if (s > 0) goods.push_back({ s * w, s * v });//如果这个物品大于零,再把它加进来,原因看上面那段话
	}
	
	for (auto good : goods)//01背包问题
		for (int j = m; j >= good.v; j--)
			f[j] = max(f[j], f[j - good.v] + good.w);
	cout << f[m];
	return 0;
}

分组背包

分组背包: 每组中的物品互相冲突,最多选一件。

状态表示:

  • 状态描述: 分组背包总共有三个信息, 分别是组数、容量、总价值, 其中总价值是需要求出来的信息, 因此需要有两个维度。
  • 集合属性: 最大值
  • 集合定义 : f ( i , j ) f(i,j) f(i,j), 在前 i 组物品中, 容量不大于 j 的选法中, 总价值最大的选法。
    状态计算 : 对于集合 f ( i , j ) f(i,j) f(i,j)
  • 不选第 i 组物品 : f ( i , j ) = f ( i − 1 , j ) f(i,j)=f(i-1,j) f(i,j)=f(i−1,j)
  • 选第 i 组的第 k 个物品 : f ( i , j ) = f ( i − 1 , j − v k ) + w k f(i,j)=f(i-1,j-v_k)+w_k f(i,j)=f(i−1,j−vk)+wk, 其中 j ≥ v k j \ge v_k j≥vk
  • 状态转换方程式 : f ( i , j ) = max ⁡ { f ( i − 1 , j ) , max ⁡ k { f ( i − 1 , j − v k ) + w k } } f(i,j)=\max\{f(i-1,j), \max_{k} \{f(i-1,j-v_k)+w_k\} \} f(i,j)=max{f(i−1,j),maxk{f(i−1,j−vk)+wk}}
cpp 复制代码
#include <iostream>
using namespace std;

const int N=110;
int f[N];
int v[N][N],w[N][N]; 
int s[N];
int n,m;

int main()
{
	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(j >= v[i][k])
                    f[j]=max(f[j], f[j-v[i][k]] + w[i][k]);

	cout<<f[m]<<endl;
	return 0;
}

混合背包问题

混合背包就是把 01背包 、完全背包、多重背包 都混在同一题目里面, 做的时候就把它们分成三个情况去处理就可以了。

cpp 复制代码
#include <iostream>
#include <vector>
using namespace std;

const int N=1010;
struct Thing
{
	int kind;
	int v,w;
};
vector<Thing> things;
int f[N];
int n,m;

int main()
{
	ios::sync_with_stdio(0),cin.tie(0),cin.tie(0);
	cin>>n>>m;
	for(int i=0;i<n;i++)//统一化处理 把多重背包转化成01背包问题
	{
		int v,w,s;
		cin>>v>>w>>s;
		if(s==-1) things.push_back({-1,v,w}); // 01背包
		else if (s==0) things.push_back({0,v,w}); // 完全背包
		else 
		{
			for(int k=1;k<=s;k*=2) // 多重背包二进制拆分 
			{
				s-=k;
				things.push_back({-1,k*v,k*w});
			}
			if(s>0) things.push_back({-1,s*v,s*w});
		}
	}
	
	for(auto thing:things)
	{
		if(thing.kind==0) //完全背包
		{
			for(int i=thing.v;i<=m;i++) f[i]=max(f[i],f[i-thing.v]+thing.w);
		}
		else //01背包 (多重背包拆分后也按01背包处理)
		{
			for(int i=m;i>=thing.v;i--) f[i]=max(f[i],f[i-thing.v]+thing.w);
		}
	}
	cout<<f[m]<<endl;
	return 0;
}

有依赖的背包问题

类似于一颗树 选了这个节点就一定要选它的父节点, 因此类似于树状DP+背包问题, 因为是树状DP所以 i 从 1 开始比较方便。

状态表示:

  • 状态描述: 有依赖的背包问题总共有三个信息, 分别是物品(节点)、容量、总价值, 其中总价值是需要求出来的信息, 因此需要有两个维度。
  • 集合属性: 最大值
  • 集合定义 : f ( u , j ) f(u,j) f(u,j), 在以节点 u 为根的子树中选择,且总体积不超过 j 的所有选法中,能获得的最大价值 (前提是节点 u 必选)。
    状态计算 : 对于集合 f ( u , j ) f(u,j) f(u,j)
  • 不选第 i 个物品 : f ( i , j ) = 0 f(i,j)=0 f(i,j)=0, 因为不选第 i 个物品, 那么就不能选以它为根节点的所有子节点的物品。
  • 选第 i 个物品 : f ( i , j ) = w f(i,j)=w f(i,j)=w, 其中 j ≥ v j \ge v j≥v。
  • 选第 i 个物品而合并子树 : f ( i , j ) = f ( i , j − k ) + f ( s o n , k ) f(i,j)=f(i,j-k)+f(son,k) f(i,j)=f(i,j−k)+f(son,k), 其中 j ≥ v j \ge v j≥v。
  • 状态转换方程式 : f ( i , j ) = max ⁡ { 0 , w , f ( i , j − k ) + f ( s o n , k ) } = max ⁡ { w , f ( i , j − k ) + f ( s o n , k ) } f(i,j)=\max\{0,w, f(i,j-k)+f(son,k)\}=\max\{w, f(i,j-k)+f(son,k)\} f(i,j)=max{0,w,f(i,j−k)+f(son,k)}=max{w,f(i,j−k)+f(son,k)}
cpp 复制代码
#include <iostream>
#include <cstring>
using namespace std;

const int N=110;
int f[N][N];
int v[N],w[N];
int h[N],e[N],ne[N],idx;
int n,m;

void add(int a,int b)
{
	e[idx]=b;
	ne[idx]=h[a];
	h[a]=idx++;
}

void dfs(int u)
{
	for(int i=v[u];i<=m;i++) f[u][i]=w[u];
	
	for(int i=h[u];i!=-1;i=ne[i])
	{
		int son=e[i];
		dfs(son);  // 递归处理子树
		for(int j=m;j>=v[u];j--) 
			for(int k=0;k<=j-v[u];k++)
				f[u][j]=max(f[u][j],f[u][j-k]+f[son][k]);
	}
}

int main()
{
	memset(h,-1,sizeof h);
	cin>>n>>m;
	int root;
	for(int i=1;i<=n;i++)
	{
		int p;
		cin>>v[i]>>w[i]>>p;
		if(p==-1) root=i;
		else add(p,i);
	}
	
	dfs(root);
	cout<<f[root][m];
	
	return 0;
}

背包问题求方案数

因为如果在前 i 个物品中, 容量不大于 j 的选法中, 总价值最大的选法, 那么就不方便去统计方案数, 所以我们把它改成在前 i 个物品中, 容量恰好为 j 的选法。

cpp 复制代码
#include <iostream>
using namespace std;

const int N=1010,mod=1e9+7;
int f[N],cnt[N];  
int n,m;

int main()
{
	cin>>n>>m;
    for(int i=1; i<=m; ++i) f[i] = -1e9; 
	f[0] = 0; 
    cnt[0]=1; 

	for(int i=0;i<n;i++)
	{
		int v,w;
		cin>>v>>w;
		for(int j=m;j>=v;j--)
		{
			int val_select = f[j-v]+w; 
            int val_not_select = f[j];  
            
            if (val_select > val_not_select) {
                f[j] = val_select;
                cnt[j] = cnt[j-v];
            } else if (val_select == val_not_select) {
                cnt[j] = (cnt[j] + cnt[j-v]) % mod;
            }
		}
	}
	
	cout<<cnt[m]<<endl; 

	return 0;
}

背包问题求具体方案

先求出正确答案 然后再反推过程 所以就不能用滚动数组去存储值, 否则前面的记录就有可能被覆盖。

如果要求的是字典序最小的话 因为我们反推是从后往前推的 所以我们求DP的时候就反过来求 从n求到1 那样反推回去的话就是字典序最小的方案。

cpp 复制代码
#include <iostream>
#include <vector> 
using namespace std;

const int N=1010;
int f[N][N];
int v[N],w[N];
int n,m;

int main()
{
	cin>>n>>m;
	for(int i=1;i<=n;i++) cin>>v[i]>>w[i];
	
	for(int i=n;i>=1;i--) //背包问题 但因为要的是最小的字典序所以从后向前去DP
	{
		for(int j=0;j<=m;j++)
		{
			f[i][j]=f[i+1][j];
			if(j>=v[i]) f[i][j]=max(f[i][j],f[i+1][j-v[i]]+w[i]);
		}
	}
	
	int current_vol=m;
    vector<int> ans;
	for(int i=1;i<=n;i++)
	{
		if(current_vol>=v[i] && f[i][current_vol]==f[i+1][current_vol-v[i]]+w[i]) 
		{
			ans.push_back(i);
			current_vol-=v[i];
		}
	}
    for (int i = 0; i < ans.size(); ++i) {
        cout << ans[i] << (i == ans.size() - 1 ? "" : " ");
    }
	return 0;
}

二维费用的背包问题

8. 二维费用的背包问题 - AcWing题库

就是把 01背包 的一维情况扩写成 二维

把维数增加一维然后枚举的时候写多一个循环。

cpp 复制代码
#include <iostream>
using namespace std;

const int N=110;
int f[N][N];
int n,v,m;

int main()
{
	ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
	
	cin>>n>>v>>m;
	for(int i=0;i<n;i++)
	{
		int a,b,c;
		cin>>a>>b>>c;
		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]+c);
	}
	cout<<f[v][m]<<'\n';
	
	return 0;
}
相关推荐
uesowys3 小时前
华为OD算法开发指导-比赛的冠亚季军
算法·华为od
天选之女wow3 小时前
【代码随想录算法训练营——Day48】单调栈——42.接雨水、84.柱状图中最大的矩形
算法·leetcode
不知名。。。。。。。。3 小时前
算法之动态规划
算法·动态规划
lingchen19063 小时前
MATLAB图形绘制基础(一)二维图形
开发语言·算法·matlab
hlpinghcg3 小时前
(全闭环)FUNC_FullCloseLoop
算法·电机·电机控制
朝新_3 小时前
【EE初阶】JVM
java·开发语言·网络·jvm·笔记·算法·javaee
wolfseek3 小时前
opencv模版匹配
c++·人工智能·opencv·计算机视觉
x70x803 小时前
git仓库基本使用
git·算法·编程
仰泳的熊猫4 小时前
LeetCode:773. 滑动谜题
数据结构·c++·算法·leetcode