【算法笔记】动态规划基础(二):背包dp
目录
- 01背包
- 例题
- 状态表示
- 状态计算
- 初始化
- AC代码
- 完全背包
- 例题
- 状态表示
- 状态计算
- 初始化
- TLE代码
- 多重背包
- 例题
- 状态表示
- 状态计算
- 初始化
- AC代码
- 分组背包
- 例题
- 状态表示
- 状态计算
- 初始化
- AC代码
- 二维费用背包
- 例题
- 状态表示
- 状态计算
- 初始化
- AC代码
- 混合背包问题
- 例题
- 状态表示
- 状态计算
- 初始化
- TLE代码
- 背包问题求方案数
- 例题
- 状态表示
- 状态计算
- 初始化
- AC代码
- 背包问题求具体方案
- 例题
- 分析
- AC代码
- 背包问题求最优选法方案数
- 例题
- 状态表示
- 状态计算
- 初始化
- AC代码1
- AC代码2
- 图论写法
- 小优化
- 滚动数组优化dp
- 什么是滚动数组
- 滚动数组优化了什么
- 滚动数组优化01背包
- ACcode
- 滚动数组直接彻底优化掉01背包的一维
- 滚动数组优化完全背包
- 二进制优化多重背包
- 例题
- 分析
- 为什么这样拆分是对的?
- 一篇写的很好的文章
01背包
例题
2. 01背包问题
状态表示
分析问题,首先需要遍历每一个物品,题中要求最大价值,限制条件是最大体积,要遍历所有可能的体积,因此要开两维: d p [ i ] [ j ] dp[i][j] dp[i][j]
- 集合:所有从前 i i i个物品中选,所选物品总体积不超过 j j j的方案
- 属性:(所选物品总价值的)最大值
综上: d p [ i ] [ j ] dp[i][j] dp[i][j]表示所有从前 i i i个物品中选,所选物品总体积不超过 j j j的最大价值。
状态计算
集合划分:将这个集合划分成几个不同的集合,就要找一个不同的步骤。
最后一步就不同,有点 d f s dfs dfs指数级枚举的感觉,第 i i i个物品可以选、也可以不选。
如果不选第 i i i个物品,跟看第 i i i个物品前相比,总体积不变、总价值不变,也就是 d p [ i − 1 ] [ j ] dp[i - 1][j] dp[i−1][j]
如果选第 i i i个物品,跟看第 i i i个物品前相比,体积增加了 v [ i ] v[i] v[i],价值增加了 w [ i ] w[i] w[i],当前的总体积不超过 j j j,那选之前的体积就不超过 j − v [ i ] j - v[i] j−v[i],也就是 d p [ i − 1 ] [ j − v [ i ] ] + w [ i ] dp[i - 1][j - v[i]] + w[i] dp[i−1][j−v[i]]+w[i],这里可以看出,要保证 j − v [ i ] ≥ 0 j - v[i] \ge 0 j−v[i]≥0,也就是 j ≥ v [ i ] j \ge v[i] j≥v[i](选的物品总体积不能超过 j j j, j j j还小于 v [ i ] v[i] v[i], 那第 i i i个物品当然不能选了…)
可以得到状态转移方程:
d p [ i ] [ j ] = m a x ( d p [ i ] [ j ] , d p [ i − 1 ] [ j ] ) dp[i][j] = max(dp[i][j], dp[i - 1][j]) dp[i][j]=max(dp[i][j],dp[i−1][j])
d p [ i ] [ j ] = m a x ( d p [ i ] [ j ] , d p [ i − 1 ] [ j − v [ i ] ] + w [ i ] ) ( j ≥ v [ i ] ) dp[i][j] = max(dp[i][j], dp[i - 1][j - v[i]] + w[i]) (j \ge v[i]) dp[i][j]=max(dp[i][j],dp[i−1][j−v[i]]+w[i])(j≥v[i])
初始化
求最大价值,并且总价值也是从0开始加的,直接将所有位置初始化成0即可。
AC代码
#include <iostream>
#include <cstring>
#define endl '\n'
using namespace std;
const int N = 1010;int dp[N][N];
int v[N], w[N];int main(){int n, m;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++){dp[i][j] = dp[i - 1][j];if(j >= v[i]) dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - v[i]] + w[i]);} }cout << dp[n][m] << endl;return 0;
}
完全背包
例题
3. 完全背包问题
状态表示
和01背包分析方式相同,唯一不同的是,这次每一个物品可以选任意次,如果从 d f s dfs dfs的角度想,就是把选和不选的两种状态变成了选多少个,就是加了一层 f o r for for循环, d p dp dp也是一样的道理。还是两维,
和01背包相同:
d p [ i ] [ j ] dp[i][j] dp[i][j]表示所有从前 i i i个物品中选,所选物品总体积不超过 j j j的最大价值。
状态计算
多了一重 f o r for for循环遍历每个物品选多少个,在01背包选的基础上,选多少个也就是减多少个 v [ i ] v[i] v[i]、加多少个 w [ i ] w[i] w[i],状态转移方程也就有了。
d p [ i ] [ j ] = max 0 ≤ k ≤ t ( d p [ i ] [ j ] , d p [ i − 1 ] [ j − k ⋅ v [ i ] ] + k ⋅ w [ i ] ) , t = ⌊ j v [ i ] ⌋ dp[i][j]=\max\limits_{0\leq k \leq t} (dp[i][j], dp[i-1][j-k \cdot v[i]]+k \cdot w[i]),t=\lfloor \frac{j}{v[i]} \rfloor dp[i][j]=0≤k≤tmax(dp[i][j],dp[i−1][j−k⋅v[i]]+k⋅w[i]),t=⌊v[i]j⌋
初始化
求最大价值,并且总价值也是从0开始加的,直接将所有位置初始化成0即可。
TLE代码
#include <iostream>
#include <cstring>
#define endl '\n'
using namespace std;
const int N = 1010;int dp[N][N];
int v[N], w[N];int main(){int n, m;cin >> n >> m;for(int i = 1; i <= n; i++){for(int j = 0; j <= m; j++){for(int k = 0; 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;
}
多重背包
例题
4. 多重背包问题 I
状态表示
和完全背包相同,就是加了个限制:第 i i i个物品选的数量不能超过 s [ i ] s[i] s[i],就是第3重 f o r for for循环&&
上这个限制即可,其他都相同:
d p [ i ] [ j ] dp[i][j] dp[i][j]表示所有从前 i i i个物品中选,所选物品总体积不超过 j j j的最大价值。
状态计算
d p [ i ] [ j ] = max 0 ≤ k ≤ t , k ≤ s [ i ] ( d p [ i ] [ j ] , d p [ i − 1 ] [ j − k ⋅ v [ i ] ] + k ⋅ w [ i ] ) , t = ⌊ j v [ i ] ⌋ dp[i][j]=\max\limits_{0\leq k \leq t,k \leq s[i]} (dp[i][j], dp[i-1][j-k \cdot v[i]]+k \cdot w[i]),t=\lfloor \frac{j}{v[i]} \rfloor dp[i][j]=0≤k≤t,k≤s[i]max(dp[i][j],dp[i−1][j−k⋅v[i]]+k⋅w[i]),t=⌊v[i]j⌋
初始化
求最大价值,并且总价值也是从0开始加的,直接将所有位置初始化成0即可。
AC代码
#include <iostream>
#include <cstring>
#define endl '\n'
using namespace std;
const int N = 105;int dp[N][N];
int v[N], w[N], s[N];int main(){int n, m;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;
}
分组背包
例题
9. 分组背包问题
状态表示
分组背包问题是每组中有若干物品,每组只能不选或从中选一个,那是不是可以将01背包的第一重 f o r for for循环遍历每个物品变成遍历每组物品,再一重 f o r for for循环遍历一下组中的所有物品,看选哪个,剩下的就和01背包完全相同了。
d p [ i ] [ j ] dp[i][j] dp[i][j]表示所有从前 i i i组物品中选,所选物品总体积不超过 j j j的最大价值。
状态计算
d p [ i ] [ j ] = max ( d p [ i − 1 ] [ j ] , max 1 ≤ k ≤ s [ i ] ( d p [ i − 1 ] [ j − w [ i ] [ k ] ] + v [ i ] [ k ] ) ) dp[i][j]=\max (dp[i - 1][j],\max\limits_{1\leq k \leq s[i]} (dp[i - 1][j-w[i][k]]+v[i][k])) dp[i][j]=max(dp[i−1][j],1≤k≤s[i]max(dp[i−1][j−w[i][k]]+v[i][k]))
初始化
求最大价值,并且总价值也是从0开始加的,直接将所有位置初始化成0即可。
AC代码
#include <iostream>
#include <cstring>
#define endl '\n'
using namespace std;
const int N = 105;int s[N], v[N][N], w[N][N];
int dp[N][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++){cin >> v[i][j] >> w[i][j];}}for(int i = 1; i <= n; i++){for(int j = 0; j <= m; j++){dp[i][j] = max(dp[i][j], dp[i - 1][j]);for(int k = 1; k <= s[i]; k++){if(j >= v[i][k]) dp[i][j] = max(dp[i][j], dp[i - 1][j - v[i][k]] + w[i][k]);}}}cout << dp[n][m] << endl;return 0;
}
二维费用背包
例题
8. 二维费用的背包问题
状态表示
二维费用背包就是在标准的01背包的基础上,又加了一层限制,怎么对待体积限制的就怎么对待重量限制,就是多了一重 f o r for for循环遍历所有可能的重量,数组也对应加了一维,因此要开三维: d p [ i ] [ j ] [ k ] dp[i][j][k] dp[i][j][k]
- 集合:所有从前 i i i个物品中选,所选物品总体积不超过 j j j且总重量不超过 k k k的方案
- 属性:(所选物品总价值的)最大值
综上: d p [ i ] [ j ] [ k ] dp[i][j][k] dp[i][j][k]表示所有从前 i i i个物品中选,所选物品总体积不超过 j j j且总重量不超过 k k k的最大价值。
状态计算
其他和01背包相同,也是看选不选第 i i i个物品,不选的话总体积、总重量、总价值都不变,选的话总体积在原来的基础上增加 v [ i ] v[i] v[i],那选之前的最大体积就应该是 j − v [ i ] j - v[i] j−v[i],总重量在原来的基础上增加 m [ i ] m[i] m[i],那选之前的最大体积就应该是 k − m [ i ] k - m[i] k−m[i],总价值也加 w [ i ] w[i] w[i],能选的前提是 j ≥ v [ i ] & & k ≥ m [ i ] j \ge v[i] \&\& k \ge m[i] j≥v[i]&&k≥m[i]。
可得状态转移方程:
d p [ i ] [ j ] [ k ] = m a x ( d p [ i ] [ j ] [ k ] , d p [ i − 1 ] [ j ] [ k ] ) dp[i][j][k] = max(dp[i][j][k], dp[i - 1][j][k]) dp[i][j][k]=max(dp[i][j][k],dp[i−1][j][k])
d p [ i ] [ j ] [ k ] = m a x ( d p [ i ] [ j ] [ k ] , d p [ i − 1 ] [ j − v [ i ] ] [ k − m [ i ] ] + w [ i ] ) ( j ≥ v [ i ] & & k ≥ m [ i ] ) dp[i][j][k] = max(dp[i][j][k], dp[i - 1][j - v[i]][k - m[i]] + w[i]) (j \ge v[i] \&\& k \ge m[i]) dp[i][j][k]=max(dp[i][j][k],dp[i−1][j−v[i]][k−m[i]]+w[i])(j≥v[i]&&k≥m[i])
初始化
求最大价值,并且总价值也是从0开始加的,直接将所有位置初始化成0即可。
AC代码
#include <iostream>
#include <cstring>
#define endl '\n'
using namespace std;
const int N = 105;int dp[1010][N][N];
int v[1010], m[1010], w[1010];int main(){int n, m1, m2;cin >> n >> m1 >> m2;for(int i = 1; i <= n; i++){cin >> v[i] >> m[i] >> w[i];}for(int i = 1; i <= n; i++){for(int j = 0; j <= m1; j++){for(int k = 0; k <= m2; k++){dp[i][j][k] = max(dp[i][j][k], dp[i - 1][j][k]);if(j >= v[i] && k >= m[i]) dp[i][j][k] = max(dp[i][j][k], dp[i - 1][j - v[i]][k - m[i]] + w[i]);}}}cout << dp[n][m1][m2] << endl;return 0;
}
混合背包问题
例题
7. 混合背包问题
状态表示
观察上面的几种背包问题可以看出,01背包、完全背包、多重背包其实都是一样的,都是:
d p [ i ] [ j ] dp[i][j] dp[i][j]表示所有从前 i i i个物品中选,所选物品总体积不超过 j j j的最大价值。
状态计算
就是将上面三个背包的状态计算结合到一起,先判断一下是什么背包,然后用对应的方程转移即可。
for(遍历所有物品){if(是01背包) 套用01背包代码;else if(是完全背包)套用完全背包代码;else if(是多重背包)套用多重背包代码;
}
初始化
求最大价值,并且总价值也是从0开始加的,直接将所有位置初始化成0即可。
TLE代码
#include <iostream>
#include <cstring>
#define endl '\n'
using namespace std;
const int N = 1010;int dp[N][N];
int v[N], w[N], s[N];int main(){int n, m;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++){if(s[i] == -1 && j >= v[i]) dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - v[i]] + w[i]);else if(s[i] == 0){for(int k = 0; k * v[i] <= j; k++) dp[i][j] = max(dp[i][j], dp[i - 1][j - k * v[i]] + k * w[i]);}else{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;
}
TLE的原因是朴素的分组背包复杂度太高,需要用二进制优化(下文)
背包问题求方案数
对于给定的一个背包容量、物品费用、其他关系等的问题,求装到一定容量的方案总数。
这种问题就是把求最大值换成求和即可。
比如下面这题–01背包求方案数
例题
278. 数字组合
状态表示
和01背包相同,就是将总体积不超过 j j j变成了总体积恰好为 j j j,把总价值换成了方案数。
- 集合:所有从前 i i i个物品中选,所选物品总体积恰好为 j j j的方案
- 属性:方案数
d p [ i ] [ j ] dp[i][j] dp[i][j]表示所有从前 i i i个物品中选,所选物品总体积恰好为 j j j的方案数。
状态计算
求方案数就是累加,其他和01背包相同。
d p [ i ] [ j ] + = d p [ i − 1 ] [ j ] dp[i][j] += dp[i - 1][j] dp[i][j]+=dp[i−1][j]
d p [ i ] [ j ] + = d p [ i − 1 ] [ j − v [ i ] ] ( j ≥ v [ i ] ) dp[i][j] += dp[i - 1][j - v[i]](j \ge v[i]) dp[i][j]+=dp[i−1][j−v[i]](j≥v[i])
初始化
d p [ 1 ] [ 0 ] dp[1][0] dp[1][0]是从 d p [ 0 ] [ 0 ] dp[0][0] dp[0][0]转移过来的, d p [ 0 ] [ 0 ] dp[0][0] dp[0][0]是什么都不选,只有这一种方案,方案数为1。
AC代码
#include <iostream>
#include <cstring>
#define endl '\n'
using namespace std;
const int N = 10005;int dp[105][N];
int v[105];int main(){int n, m;cin >> n >> m;for(int i = 1; i <= n; i++){cin >> v[i];}dp[0][0] = 1;for(int i = 1; i <= n; i++){for(int j = 0; j <= m; j++){dp[i][j] += dp[i - 1][j];if(j >= v[i]) dp[i][j] += dp[i - 1][j - v[i]];}}cout << dp[n][m] << endl;return 0;
}
背包问题求具体方案
例题
12. 背包问题求具体方案
分析
一般而言,背包问题是要求一个最优值,如果要求输出这个最优值的方案,可以参照一般动态规划问题输出方案的方法:记录下每个状态的最优值是由状态转移方程的哪一项推出来的,换句话说,记录下它是由哪一个策略推出来的。便可根据这条策略找到上一个状态,从上一个状态接着向前推即可。
int v = V; // 记录当前的存储空间// 因为最后一件物品存储的是最终状态,所以从最后一件物品进行循环
for (从最后一件循环至第一件) {if (g[i][v]) {选了第 i 项物品;v -= 第 i 项物品的重量;} else {未选第 i 项物品;}
}
而对于求字典序最小的方案,可以从状态转移来分析一下:
对于从 1 1 1到 n n n中的每个物品,有三种情况:
- 只能选,则必须选。
- 不能选,则必不选。
- 可选课不选,则必须选。(在前面的物品能选的情况下优先选择前面的物品)
为了满足上面的条件,可以从第 n n n个物品遍历到第 1 1 1个物品,每次求出当前背包的最大总价值 d p [ 1 ] [ m ] dp[1][m] dp[1][m]
然后再从第 1 1 1个物品遍历到第 n n n个物品,其中 d p [ i ] [ j ] dp[i][j] dp[i][j]为当前的最优情况,此时若满足:
- d p [ i ] [ j ] = = d p [ i + 1 ] [ j ] dp[i][j] == dp[i + 1][j] dp[i][j]==dp[i+1][j] ,则表示 d p [ i ] [ j ] dp[i][j] dp[i][j]是由 d p [ i + 1 ] [ j ] dp[i + 1][j] dp[i+1][j]转移过来的。
- d p [ i ] [ j ] = = d p [ i + 1 ] [ j − v [ i ] ] + w [ i ] dp[i][j] == dp[i + 1][j - v[i]] + w[i] dp[i][j]==dp[i+1][j−v[i]]+w[i],则表示 d p [ i ] [ j ] dp[i][j] dp[i][j]是由 d p [ i + 1 ] [ j − v [ i ] ] dp[i + 1][j - v[i]] dp[i+1][j−v[i]]转移过来的。
这时,如果上面两个只满足一个,是“不选”转移过来的就不用管,是“选”转移过来的就输出,并减掉对应的体积即可。如果两个都满足的话,为了保证字典序最小,也要输出并减掉对应的体积。(可选可不选的时候一定选。)
AC代码
#include <iostream>
#include <cstring>
#define endl '\n'
using namespace std;
const int N = 1010;
int dp[N][N];
int v[N], w[N];int main(){int n, m;cin >> n >> m;for(int i = 1; i <= n; i++){cin >> v[i] >> w[i];}for(int i = n; i >= 1; i--){for(int j = 0; j <= m; j++){dp[i][j] = dp[i + 1][j];if(j >= v[i]) dp[i][j] = max(dp[i][j], dp[i + 1][j - v[i]] + w[i]);}}int j = m;for(int i = 1; i <= n; i++){if(j >= v[i] && dp[i][j] == dp[i + 1][j - v[i]] + w[i]){cout << i << ' ';j -= v[i];}}cout << endl;return 0;
}
背包问题求最优选法方案数
例题
11. 背包问题求方案数
状态表示
和01背包求方案数思路大致相同,但要开两个数组,一个记录最优方案,一个记录方案数。
d p [ i ] [ j ] dp[i][j] dp[i][j]表示所有从前 i i i个物品中选,所选物品总体积恰好为 j j j的最大价值。
c n t [ i ] [ j ] cnt[i][j] cnt[i][j]表示所有从前 i i i个物品中选,所选物品总体积恰好为 j j j,并且价值达到 d p [ i ] [ j ] dp[i][j] dp[i][j]的方案数。
状态计算
首先 d p dp dp数组和01背包的更新完全一致,主要来看 c n t cnt cnt数组是怎么更新的。
这里要注意一个关键点:价值达到 d p [ i ] [ j ] dp[i][j] dp[i][j]的方案数。
那你就要想:它的价值是怎么达到 d p [ i ] [ j ] dp[i][j] dp[i][j]的。
是不是有两种方式,一种是不选( d p [ i − 1 ] [ j ] dp[i - 1][j] dp[i−1][j]),一种是选( d p [ i − 1 ] [ j − v [ i ] ] + w [ i ] dp[i - 1][j - v[i]] + w[i] dp[i−1][j−v[i]]+w[i]),这两个取max,转移来的 d p [ i ] [ j ] dp[i][j] dp[i][j],那是不是 d p [ i ] [ j ] dp[i][j] dp[i][j]一定等于二者中较大的,并且是由较大的那个分支转移过来的。
d p [ i ] [ j ] dp[i][j] dp[i][j]由哪个状态转移过来, c n t [ i ] [ j ] cnt[i][j] cnt[i][j]就要累加哪个分支。
如果 d p [ i ] [ j ] = d p [ i − 1 ] [ j ] dp[i][j]=dp[i - 1][j] dp[i][j]=dp[i−1][j] 且 d p [ i ] [ j ] ≠ d p [ i − 1 ] [ j − v [ i ] ] + w [ i ] dp[i][j]\neq dp[i - 1][j - v[i]] + w[i] dp[i][j]=dp[i−1][j−v[i]]+w[i],说明我们此时不选择把物品放入背包更优, d p [ i ] [ j ] dp[i][j] dp[i][j]由 d p [ i − 1 ] [ j ] dp[i - 1][j] dp[i−1][j] 转移过来, c n t [ i ] [ j ] + = c n t [ i − 1 ] [ j ] cnt[i][j]+=cnt[i - 1][j] cnt[i][j]+=cnt[i−1][j]
如果 d p [ i ] [ j ] ≠ d p [ i − 1 ] [ j ] dp[i][j]\neq dp[i - 1][j] dp[i][j]=dp[i−1][j] 且 d p [ i ] [ j ] = d p [ i − 1 ] [ j − v [ i ] ] + w [ i ] dp[i][j]=dp[i - 1][j - v[i]] + w[i] dp[i][j]=dp[i−1][j−v[i]]+w[i],说明我们此时选择把物品放入背包更优, d p [ i ] [ j ] dp[i][j] dp[i][j]由 d p [ i − 1 ] [ j − v [ i ] ] + w [ i ] dp[i - 1][j - v[i]] + w[i] dp[i−1][j−v[i]]+w[i] 转移过来, c n t [ i ] [ j ] + = c n t [ i − 1 ] [ j − v [ i ] ] cnt[i][j] += cnt[i -1][j - v[i]] cnt[i][j]+=cnt[i−1][j−v[i]]
如果 d p [ i ] [ j ] = d p [ i − 1 ] [ j ] dp[i][j]=dp[i - 1][j] dp[i][j]=dp[i−1][j] 且 d p [ i ] [ j ] = d p [ i − 1 ] [ j − v [ i ] ] + w [ i ] dp[i][j]=dp[i - 1][j - v[i]] + w[i] dp[i][j]=dp[i−1][j−v[i]]+w[i],说明放入或不放入都能取得最优解, d p [ i ] [ j ] dp[i][j] dp[i][j]由 d p [ i − 1 ] [ j ] dp[i - 1][j] dp[i−1][j] 和 d p [ i − 1 ] [ j − v [ i ] ] + w [ i ] dp[i - 1][j - v[i]] +w[i] dp[i−1][j−v[i]]+w[i] 转移过来, c n t [ i ] [ j ] + = c n t [ i − 1 ] [ j ] cnt[i][j]+=cnt[i - 1][j] cnt[i][j]+=cnt[i−1][j], c n t [ i ] [ j ] + = c n t [ i − 1 ] [ j − v [ i ] ] ; cnt[i][j] += cnt[i -1][j - v[i]]; cnt[i][j]+=cnt[i−1][j−v[i]];
如果理解不了的话,从图论跑最长路、最长路计数的角度想一下:
if(dp[i - 1][j] > dp[i][j]){dp[i][j] = dp[i - 1][j]; // dp[i][j] == dp[i - 1][j]就说明是从dp[i - 1][j]转移过来的cnt[i][j] += cnt[i - 1][j];
}if(dp[i - 1][j - v[i]] + w[i] > dp[i][j]{dp[i][j] = dp[i - 1][j - v[i]] + w[i]; // dp[i][j] == dp[i - 1][j - v[i]] + w[i]就说明是从dp[i - 1][j - v[i]]转移过来的cnt[i][j] += cnt[i - 1][j - v[i]];
}
初始化
c n t [ 1 ] [ 0 ] cnt[1][0] cnt[1][0]是从 c n t [ 0 ] [ 0 ] cnt[0][0] cnt[0][0]转移过来的, c n t [ 0 ] [ 0 ] cnt[0][0] cnt[0][0]是什么都不选,只有这一种方案,方案数为1。
需要注意, d p dp dp数组的初始化和普通的01背包不太一样:普通的01背包的 j j j表示总体积不超过 j j j,是存在“没装满”的情况的,而当前定义的是总体积恰好等于 j j j,一定不存在“没装满”的情况,那怎么从根源上避免这种“没装满”的情况呢?
想一下,所有的那些“没装满”的状态,都是由哪几个“起点”转移过去的,也就是最极限的“没装满”是哪几个状态。
可以看一下下标为0的几个位置,对于所有的 d p [ 0 ] [ j ] dp[0][j] dp[0][j],一个物品都没选,还能有总体积?所有的“没装满”的状态都是由这些“起点”转移过去的,所以给这些点都初始化成负无穷,不让他们转移,就从根源上筛掉了所有的“没装满”的状态。
AC代码1
#include <iostream>
#include <cstring>
#define endl '\n'
using namespace std;
const int N = 1010;
const int mod = 1e9 + 7;int dp[N][N], cnt[N][N];
int v[N], w[N];int main(){int n, m;cin >> n >> m;for(int i = 1; i <= n; i++){cin >> v[i] >> w[i];}for(int i = 1; i <= m; i++) dp[0][i] = -0x3f3f3f3f;cnt[0][0] = 1;for(int i = 1; i <= n; i++){for(int j = 0; j <= m; j++){dp[i][j] = max(dp[i][j], dp[i - 1][j]);if(j >= v[i]) dp[i][j] = max(dp[i][j], dp[i - 1][j - v[i]] + w[i]);}}for(int i = 1; i <= n; i++){for(int j = 0; j <= m; j++){if(dp[i][j] == dp[i - 1][j]) cnt[i][j] = (cnt[i][j] + cnt[i - 1][j]) % mod;if(j >= v[i] && dp[i][j] == dp[i - 1][j - v[i]] + w[i]) cnt[i][j] = (cnt[i][j] + cnt[i - 1][j - v[i]]) % mod;}}int res = 0;for(int i = 0; i <= m; i++){if(dp[n][i] == dp[n][m]) res = (res + cnt[n][i]) % mod;}cout << res << endl;return 0;
}
AC代码2
#include <iostream>
#include <cstring>
#define endl '\n'
using namespace std;
const int N = 1010;
const int mod = 1e9 + 7;int dp[N][N], cnt[N][N];
int v[N], w[N];int main(){int n, m;cin >> n >> m;for(int i = 1; i <= n; i++){cin >> v[i] >> w[i];}for(int i = 1; i <= m; i++) dp[0][i] = -0x3f3f3f3f;cnt[0][0] = 1;for(int i = 1; i <= n; i++){for(int j = 0; j <= m; j++){int no = dp[i - 1][j];if(no > dp[i][j]){dp[i][j] = no;cnt[i][j] = cnt[i - 1][j];}else if(dp[i - 1][j] == dp[i][j]){cnt[i][j] += cnt[i - 1][j];}if(j >= v[i]){int yes = dp[i - 1][j - v[i]] + w[i];if(yes > dp[i][j]){dp[i][j] = yes;cnt[i][j] = cnt[i - 1][j - v[i]];}else if(yes == dp[i][j]){cnt[i][j] = (cnt[i][j] + cnt[i - 1][j - v[i]]) % mod;}}}}int res = 0;for(int i = 0; i <= m; i++){if(dp[n][i] == dp[n][m]) res = (res + cnt[n][i]) % mod;}cout << res << endl;return 0;
}
图论写法
像这种 d p dp dp求方案数、求方案的题,都可以可以将其转化为图论中在DAG上的最短路的问题,感兴趣可以自己写一下。
小优化
根据贪心原理,当费用相同时,只需保留价值最高的;当价值一定时,只需保留费用最低的;当有两件物品 i i i, j j j 且 i i i 的价值大于 j j j 的价值并且 i i i 的费用小于 j j j 的费用时,只需保留 i i i。
滚动数组优化dp
什么是滚动数组
d p dp dp的问题最常用的优化之一:滚动数组优化 d p dp dp状态表示,主要用来优化空间。
滚动数组优化了什么
对于有的题, d p dp dp的状态有很多个, d p dp dp数组也要开很多维,空间严重超限(比如 d p [ 1000 ] [ 1000 ] [ 1000 ] dp[1000][1000][1000] dp[1000][1000][1000]),这个时候,如果状态转移的某一维满足某些条件,就可以用滚动数组将 d p dp dp数组的其中一维优化成 2 2 2(比如 d p [ 2 ] [ 1000 ] [ 1000 ] dp[2][1000][1000] dp[2][1000][1000]),甚至有的时候还可以直接优化掉一维。
滚动数组优化01背包
下面用01背包问题为例。
01背包的状态转移方程:
d p [ i ] [ j ] = m a x ( d p [ i ] [ j ] , d p [ i − 1 ] [ j ] ) dp[i][j] = max(dp[i][j], dp[i - 1][j]) dp[i][j]=max(dp[i][j],dp[i−1][j])
d p [ i ] [ j ] = m a x ( d p [ i ] [ j ] , d p [ i − 1 ] [ j − v [ i ] ] + w [ i ] ) ( j ≥ v [ i ] ) dp[i][j] = max(dp[i][j], dp[i - 1][j - v[i]] + w[i]) (j \ge v[i]) dp[i][j]=max(dp[i][j],dp[i−1][j−v[i]]+w[i])(j≥v[i])
可以看到,对于每次状态转移,都是 d p [ i − 1 ] [ . . . ] dp[i - 1][...] dp[i−1][...]向 d p [ i ] [ . . . ] dp[i][...] dp[i][...]转移,也就是每次转移时, d p dp dp的第一维用到的空间只有 2 2 2,那可不可以每次让 d p dp dp第一维只开 2 2 2,然后让现在的 i i i变成在这两维之间来回滚动呢?
观察一下,对于所有的 i i i和 i + 1 i + 1 i+1,是不是只要 i i i是奇数, i + 1 i + 1 i+1就必然是偶数;只要 i i i是偶数, i + 1 i + 1 i+1就必然是奇数;
那在每次表示和转移 d p dp dp数组时都给第一维 % 2 \%2 %2,或者给第一维 & 1 \&1 &1,是不是就实现了在 0 0 0和 1 1 1之间反复滚动?
拿 i % 2 i \% 2 i%2举例子:
- 如果 i i i是奇数, i % 2 = 1 i \% 2 = 1 i%2=1, ( i − 1 ) % 2 = 0 (i - 1) \% 2 = 0 (i−1)%2=0,实际上就是 d p [ 0 ] [ . . . ] dp[0][...] dp[0][...]向 d p [ 1 ] [ . . . ] dp[1][...] dp[1][...]转移,这时 i + 1 i + 1 i+1是偶数,下一次又是 d p [ 1 ] [ . . . ] dp[1][...] dp[1][...]向 d p [ 0 ] [ . . . ] dp[0][...] dp[0][...]转移,这时的 d p [ 0 ] [ . . . ] dp[0][...] dp[0][...]已经是 ( i + 1 ) − 2 (i + 1) - 2 (i+1)−2的结果了,覆盖不会影响 i + 1 i + 1 i+1的结果,所以可以这么做。
- 如果 i i i是偶数也同理。
就是这样让 d p dp dp数组的某一维在 0 0 0和 1 1 1之间来回滚动,从而优化掉一维空间,这种优化方式就叫滚动数组。
ACcode
#include <iostream>
#include <cstring>
#define endl '\n'
using namespace std;
const int N = 1010;int dp[2][N];
int v[N], w[N];int main(){int n, m;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++){dp[i & 1][j] = dp[i - 1 & 1][j];if(j >= v[i]) dp[i & 1][j] = max(dp[i - 1 & 1][j], dp[i - 1 & 1][j - v[i]] + w[i]);} }cout << dp[n & 1][m] << endl;return 0;
}
滚动数组直接彻底优化掉01背包的一维
观察上面的代码,容易发现:在每个阶段开始时,实际上执行了一次从 d p [ i − 1 ] [ ] dp[i - 1][] dp[i−1][]到 d p [ i ] [ ] dp[i][] dp[i][]的拷贝操作( d p [ i & 1 ] [ j ] = d p [ i − 1 & 1 ] [ j ] dp[i \& 1][j] = dp[i - 1 \& 1][j] dp[i&1][j]=dp[i−1&1][j]),这提示我们可以进一步省略掉 d p dp dp数组的第一维,只用一维数组,即当外层循环到第 i i i个物品时, d p [ j ] dp[j] dp[j]表示背包中放的物品的总体积不超过 j j j的最大价值和。
#include <iostream>
#include <cstring>
#define endl '\n'
using namespace std;
const int N = 1010;int dp[N];
int v[N], w[N];int main(){int n, m;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;
}
但如果直接优化掉一维的话,遍历体积的一维一定要倒序循环,为什么呢?
每次 d p dp dp更新时,都是由 d p [ i − 1 ] [ ] dp[i - 1][] dp[i−1][]向 d p [ i ] [ ] dp[i][] dp[i][]更新,每一次用到的一定是上一层的数据。
把 d p [ i − 1 ] [ j − v [ i ] ] + w [ i ] − > d p [ i ] [ j ] dp[i - 1][j - v[i]] + w[i] -> dp[i][j] dp[i−1][j−v[i]]+w[i]−>dp[i][j]换成一维的话,就是 d p [ j − v [ i ] ] + w [ i ] − > d p [ j ] dp[j - v[i]] + w[i] -> dp[j] dp[j−v[i]]+w[i]−>dp[j],当我们从小到大更新时, 因为 j − v [ i ] j - v[i] j−v[i] 是严格小于 j j j 的,所以我们可以举个例子 d p [ 3 ] = m a x ( d p [ 3 ] , d p [ 2 ] + 1 ) dp[3] = max(dp[3], dp[2] + 1) dp[3]=max(dp[3],dp[2]+1) 因为我们是从小到大更新的,所以当更新到 d p [ 3 ] dp[3] dp[3]的时候, d p [ 2 ] dp[2] dp[2]已经更新过了,已经不是上一层的 d p [ 2 ] dp[2] dp[2]。
那这样会产生什么样的效果呢?为什么这样不行呢?想一下,更新 d p dp dp数组,一定是先遍历 i i i、后遍历 j j j,每次遍历 j j j的时候实际上 i i i是确定的,也就是同一个物品。如果 d p [ j − v [ i ] ] dp[j - v[i]] dp[j−v[i]]在前面已经被更新过了,也就是说前面的转移过程是 d p [ j − v [ i ] − v [ i ] ] + w [ i ] − > d p [ j − v [ i ] ] dp[j - v[i] - v[i]] + w[i] -> dp[j - v[i]] dp[j−v[i]−v[i]]+w[i]−>dp[j−v[i]],如果后面再拿 d p [ j − v [ i ] ] dp[j - v[i]] dp[j−v[i]]去更新 d p [ j ] dp[j] dp[j],也就是 d p [ j − v [ i ] ] + w [ i ] − > d p [ j ] dp[j - v[i]] + w[i] -> dp[j] dp[j−v[i]]+w[i]−>dp[j],那最后的效果就是 d p [ j − 2 ∗ v [ i ] ] + 2 ∗ w [ i ] dp[j - 2 * v[i]] + 2 * w[i] dp[j−2∗v[i]]+2∗w[i],那是不是意味着在当前 d p [ j ] dp[j] dp[j]这个状态,第 i i i个物品被选了两次?如果在后面再更新,还会被选更多次,这就和01背包一件物品只能选一次冲突了。
倒序遍历体积就避免了上面的问题。
滚动数组优化完全背包
弄懂滚动数组优化01背包为什么要倒序遍历体积后,在刚才的过程中想一想,如果正序遍历,就会出现一个物品可以选多次的情况,那一个物品选多次,不就是完全背包吗?所以在01背包滚动数组优化成一维的基础上,正序遍历体积,就变成了滚动数组优化完全背包,比如上面的完全背包问题,下面就是它的AC代码。
#include <iostream>
#include <cstring>
#define endl '\n'
using namespace std;
const int N = 1010;int dp[N];
int v[N], w[N];int main(){int n, m;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;
}
二进制优化多重背包
例题
多重背包问题 II
分析
优化的本质就是:用二进制的思想将每个物品可以选的次数 s s s拆分。
首先,将多重背包转化成最朴素的01背包:也就是每种物品有 s s s个,将每一个都看成是01背包中的1个物品,每个物品都只有选 0 0 0个和选 1 1 1个这两种选法。
但这样来选,复杂度太高了,可不可以将某些物品放在一堆呢?
比如第 i i i件物品有 s s s件:
首先,朴素的多重背包问题,是不是要从 1 1 1~ s s s遍历一遍?
那如果能将这 s s s个物品分成几堆,通过选这几堆其中的几堆物品,他们的和能凑出来从 1 1 1~ s s s的所有情况,是不是就可以通过遍历这几堆来替代这层枚举了?
进而想到,任何一个数都有它的二进制表示,任何一个数都可以表示成若干二的整数次幂的和。
举个例子:对于 s = 1023 s=1023 s=1023:
用 2 0 2^0 20可以凑出来 0 0 0~ 1 1 1的每个数。
用 2 0 2^0 20和 2 1 2^1 21可以凑出来 0 0 0~ 3 3 3的每个数。
用 2 0 2^0 20和 2 1 2^1 21和 2 2 2^2 22可以凑出来 0 0 0~ 7 7 7的每个数。
…
所以用 2 0 2^0 20 ~ 2 9 2^9 29一定可以凑出从 0 0 0~ 1023 1023 1023的每个数。
那如果 s s s不是2的整数次幂减一怎么办呢?这个时候只需要在后面再补一个 s − (前面几项 2 k 的和) s-(前面几项2^k的和) s−(前面几项2k的和)就可以了。
比如对于 s = 10 s = 10 s=10
用 2 0 2^0 20可以凑出来 0 0 0~ 1 1 1的每个数。
用 2 0 2^0 20和 2 1 2^1 21可以凑出来 0 0 0~ 3 3 3的每个数。
用 2 0 2^0 20和 2 1 2^1 21和 2 2 2^2 22可以凑出来 0 0 0~ 7 7 7的每个数。
用 2 0 2^0 20和 2 1 2^1 21和 2 2 2^2 22和 3 3 3可以凑出来从 0 0 0~ 10 10 10的每个数。
那这样分堆有什么用呢?
仔细想想,从 1 1 1~ s s s之间的每一个数都能用上面这几个堆的数凑出来,是不是每种凑法就相当于是这个数的一个二进制表示(除了多最后一个数的时候)?
而一个数的二进制表示只有0和1,那是不是问题可以转化为01背包?
事实上,最后多出来那个非 2 k 2^k 2k的数在凑数的时候也只能选0或1次。
那为什么不直接把它也用一个 > s >s >s的 2 k 2^k 2k的数来表示呢?
还是上面的例子:
对于 s = 10 s = 10 s=10
用 2 0 2^0 20可以凑出来 0 0 0~ 1 1 1的每个数。
用 2 0 2^0 20和 2 1 2^1 21可以凑出来 0 0 0~ 3 3 3的每个数。
用 2 0 2^0 20和 2 1 2^1 21和 2 2 2^2 22可以凑出来 0 0 0~ 7 7 7的每个数。
用 2 0 2^0 20和 2 1 2^1 21和 2 2 2^2 22和 3 3 3可以凑出来从 0 0 0~ 10 10 10的每个数。
用 2 0 2^0 20和 2 1 2^1 21和 2 2 2^2 22和 2 3 2^3 23可以凑出来从 0 0 0~ 15 15 15的每个数。
如果最后组数放 2 3 2^3 23个而不是 3 3 3个,就会凑出来选 11 11 11~ 15 15 15次的情况,而实际上是不能选这么多次的,因此这样凑是错的。
把对于每组物品能选 1 1 1~ s s s次,转化成每堆物品只能选 0 0 0或 1 1 1次,01背包的每种选法恰好等同于分组背包的每种选法,内层循环时间复杂度由 O ( s ) O(s) O(s)降到了 O ( l o g s ) O(logs) O(logs)。
AC代码
#include <iostream>
#include <vector>
#define endl '\n'
const int N = 10010, M = 2010;
using namespace std;int dp[M];int main(){ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);int n, m;cin >> n >> m;vector<int> v, w;for(int i = 0; i < n; i++){int a, b, s;cin >> a >> b >> s;int t = 1;while(t <= s){v.push_back(a * t);w.push_back(b * t);s -= t;t *= 2;} if(s){v.push_back(a * s);w.push_back(b * s);}}n = v.size();for(int i = 0; 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;
}
为什么这样拆分是对的?
如果直接遍历转化为01背包问题,是每次都拿一个来问,取了好还是不取好,假如10个取7个最优,那么在实际的遍历过程中在第7个以后经过状态转移方程其实已经是选择“不取”好了。
现在,用二进制思想将其分堆,分成 k + 1 k+1 k+1个分别有 2 k 2^k 2k个的堆,然后拿这一堆一堆去问,我是取了好呢,还是不取好呢,经过dp选择之后,结果和拿一个一个来问的结果是完全一样的,因为dp选择的是最优结果,而任意一个实数都可以用二进制来表示,如果最终选出来10个取7个是最优的,在分堆的选择过程中分成了 2 0 = 1 , 2 1 = 2 , 2 2 = 4 , 2 3 = 8 2^0=1,2^1=2,2^2=4,2^3=8 20=1,21=2,22=4,23=8这四堆,然后去问四次,也就是拿去走dp状态转移方程,走的结果是第一堆1个,取了比不取好,第二堆2个,取了比不取好,第三堆四个,取了比不取好,第四堆8个,取了还不如不取,最后依旧是取了 1 + 2 + 4 = 7 1+2+4=7 1+2+4=7个。
为什么是这样呢?因为dp本身就是用来比较哪个更优的,在状态转移的过程中自然会完成上述询问得出相应结果,这也就是说,无论最终取几个是最优解,用二进制取出来的结果和一次一次问是完全一样的。
二进制分堆能不重不漏的表示到所有情况,一个一个遍历也能遍历到所有情况,而dp每一步的状态转移都取的是当前的最优解,两者都做到了不重不漏,逐渐递推到全局,也一定是全局的最优解。
一篇写的很好的文章
背包问题九讲 - 崔添翼
相关文章:
【算法笔记】动态规划基础(二):背包dp
目录 01背包例题状态表示状态计算初始化AC代码 完全背包例题状态表示状态计算初始化TLE代码 多重背包例题状态表示状态计算初始化AC代码 分组背包例题状态表示状态计算初始化AC代码 二维费用背包例题状态表示状态计算初始化AC代码 混合背包问题例题状态表示状态计算初始化TLE代…...
IP属地是我的定位吗?——解析两者区别
在互联网时代,我们经常看到社交媒体、论坛或APP上显示用户的“IP属地”,许多人会疑惑:IP属地是不是我的精确定位?它会不会暴露我的隐私? 本文将详细解析IP属地和定位的区别,并解答常见的相关问题&#…...
力扣每日一题1128等价多米诺骨牌对的数量
1128. 等价多米诺骨牌对的数量 题目: 给你一组多米诺骨牌 dominoes 。 形式上,dominoes[i] [a, b] 与 dominoes[j] [c, d] 等价 当且仅当 (a c 且 b d) 或者 (a d 且 b c) 。即一张骨牌可以通过旋转 0 度或 180 度得到另一张多米诺骨牌。 在 0 &l…...
SpringBoot集成CXF框架,实现WebService
SpringBoot官网地址:https://spring.io/projects/spring-ws 1、WebService服务端搭建 Maven依赖 <parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.7.17&…...
android-ndk开发(2): macOS 安装 ndk
android-ndk开发(2): macOS 安装 ndk 2025/05/05 1. 概要 对于 android-ndk 在 r23 之前的版本,官方提供了 .zip 文件, 解压即安装。 对于 android-ndk 在 r23 以及之后的版本, 官方只提供了 .dmg 文件, 不能简单的解压完成安…...
科创大赛——知识点复习【c++】——第一篇
目录 输入 一、cin 二、scanf 三、gets 四、getchar 五、fgets 输出 一、cout 二、printf 基本数据类型 一,数据类型有哪些? 二,整型(Integer Types) 1,修饰符 2,整型数据的数据范…...
硬件工程师面试常见问题(14)
第六十六问:运放--输入偏置电流和输入失调电流 输入偏置电流lb:是由于运放两个输入极都有漏电流的存在。实际的运放,会有电流流入运放的输入端的。那么输入偏置电流就定义这两个电流的平均值。 输入失调电流 Ios:定义为两个差分输入端偏置电…...
Flink流水线任务在线演示
Flink流水线在线演示 1. 登录系统 访问系统登录页面,输入账号密码完成身份验证。 2. 创建任务 入口:通过顶部菜单栏选择 任务开发,或通过快捷入口 快速创建任务。 任务类型:选择 FlinkPipeline。 3. 配置任务 进入配置界面…...
C++笔记之接口`Interface`
C++笔记之接口Interface code review! 一个简洁简短的 C++ 接口实现示例: #include <iostream>// 1. 定义接口(抽象类) class Shape {public:...
css使用aspect-ratio制作4:3和9:16和1:1等等比例布局
文章目录 1. 前言2. 用法2.1 基本语法2.2. 与max-width、max-height等属性结合使用2.3. 动态计算比例 3. 应用场景4. 兼容性和替代方案5. 总结 1. 前言 在网页制作过程中,有时候我们只知道宽度,或者只知道高度,这时候需要制作一个4:3和9:16这…...
深入探索 Apache Spark:从初识到集群运行原理
深入探索 Apache Spark:从初识到集群运行原理 在当今大数据时代,数据如同奔涌的河流,蕴藏着巨大的价值。如何高效地处理和分析这些海量数据,成为各行各业关注的焦点。Apache Spark 正是为此而生的强大引擎,它以其卓越…...
0903Redux改造项目_用户信息_状态管理-react-仿低代码平台项目
文章目录 1 Redux管理用户信息1.1 定义store和reducer1.2 使用useSeletor 2 自定义Hook统一加载用户信息存储Redux3 根据用户登录状态动态跳转页面结语 1 Redux管理用户信息 1.1 定义store和reducer src/store/userReducer.ts代码如下所示: import { createSlice…...
PyTorch_构建线性回归
使用 PyTorch 的 API 来手动构建一个线性回归的假设函数,数据加载器,损失函数,优化方法,绘制训练过程中的损失变化。 数据构建 import torch from sklearn.datasets import make_regression import matplotlib.pyplot as plt i…...
领略算法真谛: 多源bfs
嘿,各位技术潮人!好久不见甚是想念。生活就像一场奇妙冒险,而编程就是那把超酷的万能钥匙。此刻,阳光洒在键盘上,灵感在指尖跳跃,让我们抛开一切束缚,给平淡日子加点料,注入满满的pa…...
Linux的web服务器的部署及优化
实验环境的配置 我们依然是要配置本地软件仓库,之前已有详细介绍,然后再次基础上还有如下操作,首先是进入到以下文件进行编辑 编辑内容为下,并且注意自身的网关有没有写错 然后给予权限 再进行下列操作后,就配置完成了…...
ASP.NET Core 请求限速的ActionFilter
文章目录 前言一、实现步骤1)创建自定义Action Filter示例1:示例2: 2)注册服务3)使用 二、实现说明总结 前言 以下是一个基于内存缓存实现的自定义限流Action Filter。 一、实现步骤 1)创建自定义Action…...
本地化语音转换工具推荐与使用
软件介绍 Buzz是一款基于OpenAI Whisper技术开发的开源语音转文字工具,支持离线运行和实时语音转换,能够高效完成会议记录、音频转文字等任务。 安装注意事项 在使用Buzz之前需要注意软件的安装设置,由于程序自带较大的模型文件&…...
【心海资源】telegram换U地址完整源码
【心海资源】telegram换U地址完整源码 未测,需要的下载完整的 下载地址:下载地址.txt - 蓝奏云...
神经网络开发实战:从零基础到企业级应用(含CNN、RNN、BP网络代码详解)
简介 神经网络作为深度学习的核心,正在成为现代AI应用的基石。从基础的感知机到复杂的Transformer架构,从图像识别到自然语言处理,神经网络技术的演进推动了人工智能的快速发展。本文将系统介绍神经网络的核心概念、主流模型及其实现原理,并通过三个企业级实战案例(医学图…...
C# WPF 布局
C# 0、WPF 布局 1、ON/OFF按钮 2、textBox 3、ComboBox 4、TabControl 5、Button <Window x:Class"WpfApp5.MainWindow"xmlns"http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x"http://schemas.microsoft.com/winfx/20…...
【PaaS与AI融合】MLOps平台的架构设计
PaaS与AI融合:MLOps平台的架构设计 一、技术背景与发展趋势二、技术架构核心特征1. 全生命周期管理闭环2. 混合编排引擎3. 智能资源调度三、关键技术实现细节1. 持续集成流水线2. 异构资源管理3. 安全治理体系四、行业实践与未来演进典型案例分析发展趋势展望五、架构设计建议…...
硬件工程师面试常见问题(15)
第七十一问:运放增益带宽积解读(有待改进) 增益带宽积顾名思义:增益(就是开环增益)与带宽的乘积; 第七十二问:运放输出摆幅 定义:输出摆幅是指输出信号在最大值和最小值…...
SpringMVC——第6章:RESTFul编程风格
一、RESTFul编程风格 1.RESTFul是什么 RESTFul是WEB服务接口的一种设计风格。 RESTFul定义了一组约束条件和规范,可以让WEB服务接口更加简洁、易于理解、易于扩展、安全可靠。 RESTFul对一个WEB服务接口都规定了哪些东西? 对请求的URL格式有约束和规范…...
深度解析:从 GPT-4o“谄媚”到 Deepseek“物理腔”,透视大模型行为模式的底层逻辑与挑战
深度解析:从 GPT-4o“谄媚”到 AI“物理腔”,透视大模型行为模式的底层逻辑与挑战 标签:人工智能, GPT-4o, 大语言模型, AI伦理, 人机交互, 技术思考 大家好!最近AI圈最火的“瓜”之一,莫过于OpenAI的GPT-4o模型在一…...
2025 年最新树莓派 Pico 连接 OLED 显示字模汉字详细教程
OLED 概述 OLED(Organic Light-Emitting Diode,有机发光二极管)是一种基于有机材料的发光技术,通过电流驱动有机薄膜发光,具有自发光、高对比度、柔性可弯曲等特点。 4 针脚 OLED 硬件电路如图所示,GND 接…...
【Ubuntu 安装Docker CE-Jenkins】
安装Docker CE(Ubuntu) Install | Docker Docs官网 使用apt仓库安装 DNS配置(可选) #手动替换 sudo vim /etc/systemd/resolved.conf #典型配置如下 [Resolve] DNS8.8.8.8 DNS114.114.114.114 FallbackDNS1.1.1.1 # 备用 DNS#sed替换 sudo sed -i /^#DNS/ {s/#DNS/DNS8.8.8…...
知识图谱 + 大语言模型:打造更聪明、更可靠的AI大脑 —— 探索 GraphRAG 中文优化与可视化实践
大语言模型(LLMs)无疑是近年来人工智能领域最耀眼的明星。它们强大的自然语言理解和生成能力,在文本创作、代码生成、对话交互等众多领域展现了惊人的潜力。然而,当前的 LLMs 并非完美无缺,它们常常面临着“幻觉”&…...
三、【LLaMA-Factory实战】模型微调进阶:从LoRA到MoE的技术突破与工程实践
一、引言 在大模型微调领域,选择合适的训练策略直接决定了效率与效果的平衡。LLaMA-Factory深度整合了参数高效微调(PEFT)、全量微调、混合专家模型(MoE)等12种训练策略,支持从消费级GPU到多卡集群的全场景…...
Photo-SLAM论文理解、环境搭建、代码理解与实测效果
前言:第一个解耦式Photo-SLAM,亮点和效果。 参考:https://zhuanlan.zhihu.com/p/715311759 全网最细PhotoSLAM的conda环境配置教程,拒绝环境污染!!-CSDN博客 1. 环境搭建 硬件:RTX 4090D wi…...
解决pycharm检测不到已经装好的conda的pytorch环境
问题 1.找装anaconda的位置(我装到了py-anacon下) 2.找到下图中的conda.bat 3.pycharm社区版右下角,添加新解释器 4.选conda环境,选择2.中conda.bat的位置,加载环境,使用现有环境,可以看到有选…...
【计算机视觉】3d人脸重建:3DDFA_V2:实时高精度3D人脸重建与密集对齐技术指南
3d人脸重建:3DDFA_V2:实时高精度3D人脸重建与密集对齐技术指南 一、项目概述与技术背景1.1 3DDFA_V2核心价值1.2 技术演进路线1.3 核心技术指标 二、环境配置与模型部署2.1 硬件要求2.2 软件安装基础环境搭建关键组件安装 2.3 模型下载 三、核心算法原理…...
谈判模拟器 - Gemini 2.5 商业优化版
核心目标: 基于深厚的理论知识、丰富的实战经验和前沿的技术洞察,结合麦肯锡领先的谈判策略框架,为用户提供全面、深入、可操作的商业谈判策略指导和建议,助力其在复杂商业环境中达成最优谈判结果,并实现商业价值最大化…...
深度学习系统学习系列【4】之反向传播(BP)四个基本公式推导
文章目录 补充知识:∇ 和 ⊙ 运算符详解∇ (nabla) 运算符⊙ (圆圈点) 运算符 反向传播基本公式计算图和基本定义BP1:输出层误差推导BP1公式的重要性实际例子BP2第 l l l层误差推导BP3 :损失函数关于偏置(b)偏导的推导BP4: 损失函…...
算法每日一题 | 入门-顺序结构-上学迟到
上学迟到 题目描述 学校和 yyy 的家之间的距离为 s 米,而 yyy 以 v 米每分钟的速度匀速走向学校。 在上学的路上,yyy 还要额外花费 10 分钟的时间进行垃圾分类。 学校要求必须在上午 8:00 到达,请计算在不迟到的前提下,yyy 最…...
开源库测试
yolov10 https://github.com/THU-MIG/yolov10 conda create -n yolov10 python3.9 conda activate yolov10 pip install -r requirements.txt pip install -e .报错 找不到对应版本 Could not find a version that satisfies the requirement gradio4.31.5 (from versions:…...
因为gromacs必须安装cuda(系统自带的NVIDIA驱动不行),这里介绍下如何安装cuda
1. 安装步骤 查看是否安装了cuda # 法1 cat /usr/local/cuda/version.txt # 法2 nvcc --version 若没有安装,则查看是否有N卡驱动,若无N卡驱动,则到软件与更新 -> 附加驱动中安装驱动 查看N卡驱动支持的cuda版本 nvidia-smi 如下…...
ABC 404
1.C 题: 1.思路: NM&每个点读数为2,但图中有可能出现多环,需要判断所有点是否都在同一连通块上,有俩种解法:搜索,循环 2.代码(循环做法) #include<bits/stdc.h&g…...
机器学习朴素贝叶斯算法
1.朴素贝叶斯算法 1.1基本概念 其分类原理是利用贝叶斯公式根据某特征的先验概率计算出其后验概率,然后选择具有最大后验概率作为该特征所属的类。之所以称之为“朴素”,是因为贝叶斯分类只做最原始、最简单的假设:所有的特征之间是相对独立…...
Linux:深入理解数据链路层
实际上一台主机中,报文并没有通过网络层直接发送出去,而是交给了自己的下一层协议——数据链路层!! 一、理解数据链路层 网络层交付给链路层之前,会先做决策再行动(会先查一下路由表,看看目标网…...
健康养生:从生活点滴启航
养生并非遥不可及的高深学问,只需把握生活中的细微之处,就能为健康保驾护航。 清晨睁眼,先在床上做简单的搓脸动作,从下巴到额头轻柔按摩,促进面部血液循环,唤醒肌肤活力。随后空腹喝一杯温水,可…...
【向量数据库】用披萨点餐解释向量数据库:一个美味的技术类比
文章目录 前言场景设定:披萨特征向量化顾客到来:生成查询向量相似度计算实战1. 欧氏距离计算(值越小越相似)2. 余弦相似度计算(值越大越相似) 关键发现:度量选择影响结果现实启示结语 前言 想象…...
CloudCompare 中 ccDrawableObject
CloudCompare 中 ccDrawableObject 类的主要内容与使用 1. ccDrawableObject 概述 在 CloudCompare 中,ccDrawableObject 是一个基类,主要用于管理 3D 可绘制对象 的显示属性,如颜色、可见性、LOD(层次细节)、光照等…...
【Linux】进程控制
🌟🌟作者主页:ephemerals__ 🌟🌟所属专栏:Linux 目录 前言 一、什么是进程控制 二、进程创建 三、进程终止(进程退出) 退出码 main函数返回 _exit() exit() 测试 四、进…...
设计模式-基础概念学习总结(继承、多态、虚方法、方法重写)
概念使用例子的方式介绍(继承,多态,虚方法,方法重写),实现代码python 1. 继承(Inheritance) 概念:子类继承父类的属性和方法,可以直接复用父类的代码&#…...
分析rand()和srand()函数的功能
rand()和srand()函数原型: int rand(void) 返回一个范围在 0 到 RAND_MAX 之间的伪随机数。 void srand(unsigned int seed)用来给rand() 设置随机数发生器,随机数发生器输出不同的数值,rand() 就会生成不同的随机数 1)、在“D:\Keil_v5\AR…...
架构师如何构建个人IP:职业规划与业务战略的双重提升
在数字化时代,软件架构师的角色已从单纯的技术专家转变为兼具技术领导力和业务影响力的复合型人才。如何构建个人IP,提升行业影响力,成为架构师职业发展的关键课题。本文从个人认知、业务战略、架构决策、产品思维四个维度,探讨架…...
CSS知识总结
一、CSS核心概念解析 1.1 选择器体系(重点) 基础选择器: /* ID选择器 */ #header { background: #333; }/* 类选择器 */ .btn-primary { color: white; }/* 属性选择器 */ input[type"text"] { border: 1px solid #ccc; } 组合…...
CRS 16 slot 设备硬件架构
目录 1. 核心组件 1.1 线路卡与物理接口模块 1.2 交换结构与容量 1.3 控制与管理 1.4 风扇与散热 1.5 电源与告警 2. 插槽编号与机箱布局 2.1 前侧(PLIM 面) 2.2 后侧(MSC 面) 2.3 插槽配对 1. 核心组件 1.1 线路卡与物…...
人工智能浪潮中Python的核心作用与重要地位
在人工智能(Artificial Intelligence,AI)蓬勃发展的时代,Python已然成为推动这一技术进步的关键编程语言。从复杂的机器学习算法实现,到前沿的深度学习模型构建,再到智能系统的部署,Python无处不…...
【了解】数字孪生网络(Digital Twin Network,DTN)
目录 一、为什么?二、是什么?三、什么架构?四、如何应用?参考 一、为什么? 一方面,网络负载不断增加,,网络规模持续扩大带来的网络复杂性,使得网络的运行和维护变得越来越复杂。另一…...