当前位置: 首页 > news >正文

动态规划基础

动态规划

  • 动态规划概论
    • 楼梯
    • 最短路
    • 最长上升子序列(LIS)
    • 最长公共子序列(LCS)
    • 最长回文子串
  • 概率动态规划
  • 区间动态规划
    • 石子合并
    • 括号序列
    • 石子合并(环形)
  • 树形动态规划
    • 统计人数
    • 没有上司的舞会
  • 背包
    • 01背包
    • 完全背包
    • 多重背包
    • 分组背包

动态规划概论

楼梯

这给定一座有10级台阶的楼梯,我们要从下往上走,每跨一步只能向上走1级或者2级台阶,请问

一共有多少种走法呢?

方法1:搜索,我们写个dfs,依次枚举每一步向上走多少台阶,最后统计有多少可行的方案。如果要走100级台阶,搜索一年都不一定能搜索出来
方法2:组合数学我们先把走法的表示方式简化:用一个只包含1和2的序列表示从下到上每一步分别走了几级台阶;22222表示一共走了5步,每次走2级台阶,这是一种有效的走法;1111222表示一共走了7步,前四步走1级台阶,后三步走2级台阶,这也是有效的走法。接着枚举一共有几步走了2级台阶,就能算出有几步走了1级台阶,最后可以用组合数学算。10=10*1(10个1,共C(10,10)=1种)
=8*1+2(8个1,1个2,共C(9,8)=9种)
=6*1+2*2(6个1,2个2,共C(8,6)=28种)
=4*1+3*2(4个1,3个2,共C(7,4)=35种)
=2*1+4*2(2个1,4个2,共C(6,2)=15种)
=5*2(5个2,共C(5,5)=1种)共计1+9+28+35+15+1=89种。
方法3:递归考虑最后一步的情况,我们只能从第9级或者第8级走过去。不管走到第8级和第9级的过程,如果我们知道走到第8级的走法共有x种,走到第9级的走法有y种,那么走到第10级的走法呢?这一共有x+y种!我们把走到i级台阶的走法数量用f(i)来表示,有f(10)=f(8)+f(9)同理有f(9)=f(7)+f(8),f(8)=f(6)+f(7)对于任意的n≥2,有f(n)=f(n-2)+f(n-1)。(其实就是个斐波那契)
方法4 动态规划
递归计算会出现很多的重复计算
用动态规划就可以解决这个问题,从头开始推,由小问题的最优推出大问题的最优
#include <bits/stdc++.h>
using namespace std;int n;long long f[51];
int main()
{cin>>n;f[0]=1;f[1]=1;for(int i=2;i<=n;i++){f[i]=f[i-1]+f[i-2];}cout<<f[n];return 0;
}

最短路

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

#include <bits/stdc++.h>
using namespace std;int a[1001][1001],f[1001],n,m;int main()
{cin>>n>>m;memset(a,127,sizeof a);for(int i=1;i<=m;i++){int x,y,z;cin>>x>>y>>z;a[x][y]=min(a[x][y],z);}	memset(f,127,sizeof(f));f[1]=0;for(int i=2;i<=n;i++){for(int j=1;j<i;j++){if(f[j]<1<<30&&a[j][i]<1<<30){f[i]=min(f[i],f[i]+a[j][i]);}}}cout<<f[n];return 0;
}

最长上升子序列(LIS)

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

#include <bits/stdc++.h>
using namespace std;int n,a[1001],f[1001];
int main()
{cin>>n;for(int i=1;i<=n;i++){cin>>a[i];}for(int i=1;i<=n;i++){f[i]=1;for(int j=1;j<i;j++){if(a[j]<a[i]){f[i]=max(f[i],f[j]+1);}}}int ans=0;for(int i=1;i<=n;i++){ans=max(ans,f[i]);}cout<<ans;return 0;
} 

最长公共子序列(LCS)

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

#include <bits/stdc++.h>
using namespace std;int n,m,a[1001],b[1001],f[1001][1001]; 
int main()
{cin>>n>>m;for(int i=1;i<=n;i++){cin>>a[i];}for(int i=1;i<=m;i++){cin>>b[i];}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]);if(a[i]==b[j]){f[i][j]=max(f[i][j],f[i-1][j-1]+1);}}}cout<<f[n][m];return 0;
} 
#include <bits/stdc++.h>
using namespace std;int n,m,a[1001],b[1001],f[1001][1001]; 
int main()
{cin>>n>>m;for(int i=1;i<=n;i++){cin>>a[i];}for(int i=1;i<=m;i++){cin>>b[i];}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]);if(a[i]==b[j]){f[i][j]=max(f[i][j],f[i-1][j-1]+1);}}}cout<<f[n][m];return 0;
} 

最长回文子串

传送门
给定一个字符串,请你求出其中的最长回文子串的长度。

例如,给定字符串 Is PAT&TAP symmetric?,最长回文子串为 s PAT&TAP s,其长度是 11 11 11

输入格式

包含一个非空字符串。

输出格式

输出一个整数,表示给定字符串的最长回文子串的长度。

数据范围

给定字符串的长度不超过 1000 1000 1000

输入样例:

Is PAT&TAP symmetric?

输出样例:

11
方法1
由于回文串是对称的,所以我们可以枚举所有的中点,再向两侧扩散,时间复杂度为O(n^2)。

在这里插入图片描述

//中点扩散算法
#include<bits/stdc++.h>
using namespace std;
const int N=1e3+10;
int dp[N][N];
int main()
{string str;getline(cin,str);int res=0;for(int i=0;i<str.length();i++){int l=i-1,r=i+1;//判断奇数长度回文串while(l>=0&&r<str.length()&&str[l]==str[r]) l--,r++;res=max(res,r-l-1);l=i,r=i+1;//判断偶数长度回文串while(l>=0&&r<str.length()&&str[l]==str[r]) l--,r++;res=max(res,r-l-1);}cout<<res<<endl;return 0;
}
方法2 动态规划
用动态规划来做上面的那个例题,dp[N][N]来存状态,dp[ i] [ j ]=1,表示字符串从i到j为回文串。
(1)当str[ i ]==str[ j ]时,dp[i+1][j-1]=1,则dp[ i ][ j ]=1 ( [i+1,j-1]为回文串,且str[ i ]==str[ j ]则[i,j]为回文串 ),否则dp[ i ][ j ]=0。
(2)当str[ i ]!=str[ j ]时,dp[ i ][ j ]=0。           在遍历方面也要注意,如果按照i和j从小到大的顺序来枚举子串的两个端点,然后更新dp[i]lj],会无法保证dp[i + 1][ - 1]已经被计算过,从而无法得到正确的dp[i][i]。我们可以遍历长度,长度从小到达依次增加,每次判断出左右端点即可。           

在这里插入图片描述

//动态规划
#include<bits/stdc++.h>
using namespace std;
const int N=1e3+10;
bool dp[N][N];
int main()
{string str;getline(cin,str);//读入字符串int res=1;int n=str.length();for(int i=0;i<n;i++){dp[i][i]=true;if(i<n-1&&str[i]==str[i+1])//判断边界条件{res=2;dp[i][i+1]=true;}}for(int l=3;l<=n;l++)//遍历字符串长度,最小从3开始(最小为1,加上左右两边){for(int i=0;i+l-1<n;i++)//左边界{int j=i+l-1;//右边界if(str[i]==str[j]&&dp[i+1][j-1]){dp[i][j]=1;res=l;//更新时,一定l>=res}}}cout<<res<<endl;return 0;
}

概率动态规划

在这里插入图片描述
用f[x]表示能走到x号城市的概率,那么f[1]=1

从x城走到y城概率f[y]+=f[x]/d[x]
其中d[x]表示从x号城市出发的高速公路有多少条
对于每个节点 i,将节点 i 的概率 f[i] 均分给它的每个邻接节点 c[i][j],并且将这一部分概率累加到 f[c[i][j]] 中。
i = 2,c[2] = {3, 4, 5},即从节点 2 出发,有边指向节点 3、4、5。
f[2] = 0.6,表示从节点 1 到节点 2 的概率为 0.6。
l = 3,即节点 2 有 3 个邻居(3、4、5)。
f[3] += f[2] / 3; // f[3] 增加 0.6 / 3 = 0.2
f[4] += f[2] / 3; // f[4] 增加 0.6 / 3 = 0.2
f[5] += f[2] / 3; // f[5] 增加 0.6 / 3 = 0.2
#include <bits/stdc++.h>
using namesapace std;int n,m;
vector<int> c[101];
double f[101];
int main()
{cin>>n>>m;for(int i=1;i<=m;i++){int x,y;cin>>x>>y;c[x].push_back(y);}memset(f,0,sizeof (f));f[1]=1;for(int i=1;i<n;i++){int l=c[i].size();for(int j=0;j<l;j++)//边界条件不使用c[i].size,因为这是o(n)的复杂度 {f[c[i][j]]+=f[i]/l;}}return 0;
}

区间动态规划

石子合并

在这里插入图片描述
在这里插入图片描述
递归

#include <bits/stdc++.h>
using namespace std;int n,a[501],s[501];int solve(int l,int r){if(l==r){return 0;}int ans=1<<30;for(int m=l;m<r;m++){ans=min(ans,solve(l,m)+solve(m+1,r));}return ans+s[r]-s[l-1];}
int main()
{cin>>n;for(int i=l;i<=n;i++){cin>>a[i];}for(int i=l;i<=n;i++){s[i]=s[i-1]+a[i];}cout<<solve(1,n);return 0;
} 

在这里插入图片描述

#include <bits/stdc++.h>
using namespace std;int n,a[501],s[501],f[501][501];int solve(int l,int r){if(f[l][r]!=-1){return f[l][r];} if(l==r){return f[l][r]=0;}int ans=1<<30;for(int m=l;m<r;m++){ans=min(ans,solve(l,m)+solve(m+1,r));}return ans+s[r]-s[l-1];}
int main()
{cin>>n;for(int i=1;i<=n;i++){cin>>a[i];}for(int i=1;i<=n;i++){s[i]=s[i-1]+a[i];}memset(f,255,sizeof f);cout<<solve(1,n);return 0;
} 

在这里插入图片描述
在这里插入图片描述

#include <bits/stdc++.h>
using namespace std;int n,a[501],s[501],f[501][501];int main()
{cin>>n;for(int i=1;i<=n;i++){cin>>a[i];}for(int i=1;i<=n;i++){s[i]=s[i-1]+a[i];}memset(f,127,sizeof(f));for(int i=1;i<=n;i++){f[i][i]=0;}for(int i=1;i<n;i++){for(int j=1;j<=n-i;j++){for(int k=j;k<j+i;k++){f[j][j+i]=min(f[j][j+i],f[j][k]+f[k+1][j+i]+s[j+i]-s[j-1]);}}}cout<<f[1][n];return 0;
} 

括号序列

在这里插入图片描述
在这里插入图片描述

#include <bits/stdc++.h>  // 包含常用的头文件,如 iostream, vector, algorithm 等
using namespace std;int n, f[501][501];  // n: 括号序列的长度,f: 动态规划表,f[i][j] 表示从第 i 个括号到第 j 个括号的最大有效匹配括号对数
char str[511];  // 存储括号序列,大小为 511 是为了容纳括号序列及其编号从 1 开始(数组下标从 1 开始)int main()
{scanf("%d%s", &n, str + 1);  // 读取括号序列的长度 n 和括号序列字符串 str,从 str[1] 开始存储memset(f, 0, sizeof(f));  // 初始化 f 数组为 0,表示所有区间的最大匹配括号对数初始为 0// 遍历每个可能的子序列长度 ifor (int i = 1; i < n; i++) {  // i 表示当前子序列的长度,遍历从 1 到 n-1// 遍历子序列的起始位置 j,j 表示当前子序列的起始位置for (int j = 1; j <= n - i; j++) {  // j 表示起始位置,保证子序列长度为 i,不越界// 处理括号匹配的情况:首先检查是否是括号对 '()' 或 '[]' 的情况if ((str[j] == '(' && str[j + 1] == ')') || (str[j] == '[' && str[j + i] == ']')) {// 如果当前括号是匹配的括号对,则更新动态规划表f[j][j + i] = f[j + 1][j + i - 1] + 2;  // 当前匹配对加上内层匹配的得分}// 对于每个可能的分割点 k,尝试分割子串来更新最优解for (int k = j; k < j + i; k++) {  // k 是分割点,遍历 [j, j+i] 区间的所有可能分割// f[j][j+i] 表示从 j 到 j+i 的最大匹配括号对数f[j][j + i] = max(f[j][j + i], f[j][k] + f[k + 1][j + i]);// 计算通过分割子串 [j, k] 和 [k+1, j+i] 的最大得分}}}cout << f[1][n];  // 输出最终从 1 到 n 的最大有效括号对数return 0; 
}

石子合并(环形)

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

然后这也是一个非常经典的套路,就是我们这种圈的情况,我们就把它连成两倍,然后再连成两倍上面做,然后最后就是考虑f(1)(n)考虑f(2)(n+1)。就是圆圈的情况,很多情况下都是可以这样做,其实你就会把每一条边删掉,以后情况你都能够考虑进去。就f(1)(n)就是考虑的是1n的那条边删掉情况f(2) (n+1)就是2到那个n+1的那条边删掉情况f(3)到(n+2)就是31的那条边被删掉情况,你就能把所有情况。情况都考虑完。那么,这个事情复杂度就变成了n3方大,就是你会发现你把它倍增以后,就你把它一模一样,后面屁股上又接上去了,以后你n3方做完以后你就把删除,每条边的情况你都已经考虑到。

这段代码是动态规划的核心部分,目的是计算合并石子堆时最小的得分。这里 i 表示子序列的长度,而 f 数组用来存储从堆 j 到堆 j + i 的最小合并得分。

代码分析:

for (int i = 1; i <= n; i++) {for (int j = 1; j <= n - i; j++) {for (int k = j; k < j + i; k++) {f[j][j + i] = min(f[j][j + i], f[j][k] + f[k + 1][j + i] + s[j + i] - s[j - 1]);}}
}
  • 外层循环: for (int i = 1; i <= n; i++)

    • i 表示当前子问题的长度,即正在考虑的子序列的长度。
    • i1n,依次表示从长度为 1 的子序列到长度为 n 的子序列。
  • 中层循环: for (int j = 1; j <= n - i; j++)

    • j 是当前子序列的起始位置,表示从位置 j 开始,长度为 i 的子序列。
    • 由于 ji 决定了子序列的末尾位置 j + i,为了避免数组越界,j 最大值为 n - i
  • 内层循环: for (int k = j; k < j + i; k++)

    • k 用于在当前子序列 [j, j + i] 中选择一个分割点。
    • 子序列 a[j...j + i] 被划分为两部分:一部分是 a[j...k],另一部分是 a[k+1...j + i],并且 k 需要遍历整个子序列。
  • 状态转移: f[j][j + i] = min(f[j][j + i], f[j][k] + f[k + 1][j + i] + s[j + i] - s[j - 1]);

    • 这是动态规划的状态转移公式。
    • f[j][j + i] 表示将子序列 a[j...j + i] 合并成一堆的最小得分。
    • 递推关系:选择一个分割点 k,计算两部分的最小得分 f[j][k]f[k + 1][j + i],加上合并这两部分的得分(即合并后的石子数),合并后的得分是 s[j + i] - s[j - 1],即从位置 jj + i 这一部分的总石子数。
    • 通过遍历所有可能的分割点 k,我们可以得到从 jj + i 的最小得分。

具体举例:

假设我们有 4 堆石子,堆的石子数分别为 [a1, a2, a3, a4],我们希望通过合并这些堆来最小化合并得分。

  1. 第一轮(i = 1):

    • 只考虑长度为 1 的子序列(即单个堆),f[j][j + 1] 表示单堆石子合并的代价为 0,因为每次合并需要至少两堆,所以 f[j][j + 1] = 0
  2. 第二轮(i = 2):

    • 考虑长度为 2 的子序列 [j, j + 1],即选择两个相邻的堆进行合并。
    • 对于每个子序列 [j, j + 2],通过内层循环选择一个合并点 k,比如可以选择 k = jk = j + 1,计算出最小合并得分。
  3. 第三轮(i = 3):

    • 考虑长度为 3 的子序列。我们会继续选择合适的分割点,计算最小得分。
  4. 以此类推,直到 i = n,表示合并整个序列。

#include <bits/stdc++.h>  
using namespace std;int n, a[501], f[501][501], s[501];  // n: 石子堆的数量,a: 每堆石子的数量,f: 动态规划表,s: 前缀和数组int main()
{cin >> n;  // 读取石子堆的数量for (int i = 1; i <= n; i++)  // 输入每堆石子的数量{cin >> a[i];  // 读取第 i 堆的石子数量a[n + i] = a[i];  // 为了模拟环形数组,重复一遍前 n 堆石子}n *= 2;  // 扩展数组大小为 2n,处理环形问题// 计算前缀和数组 s[], s[i] 表示前 i 堆石子的总数for (int i = 1; i <= n; i++){s[i] = s[i - 1] + a[i];  // 前缀和,s[i] = s[i-1] + a[i],表示前 i 堆石子的总数}memset(f, 127, sizeof(f));  // 初始化动态规划表 f,设置为较大的数,表示最小值未计算// 动态规划计算最小合并得分for (int i = 1; i <= n; i++)  // 遍历子序列的长度 i{for (int j = 1; j <= n - i; j++)  // 遍历子序列的起始位置 j{for (int k = j; k < j + i; k++)  // 遍历分割点 k{// 计算从 j 到 j+i 的子序列合并的最小得分f[j][j + i] = min(f[j][j + i], f[j][k] + f[k + 1][j + i] + s[j + i] - s[j - 1]);// f[j][j+i] 是从堆 j 到堆 j+i 的最小合并得分// f[j][k] 和 f[k+1][j+i] 是子问题的解,s[j+i] - s[j-1] 是当前合并的代价}}}int ans = 1 << 30;  for (int i = 1; i <= n / 2; i++)  // 查找从 1 到 n/2 的最小合并得分{ans = min(ans, f[i][i + n / 2 - 1]);  // 从 f[i][i + n/2 - 1] 中寻找最小得分cout << ans << endl;  // 输出最小的得分}return 0; 
}

树形动态规划

统计人数

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
dfs写法

#include <bits/stdc++.h>
#define ll long long
using namespace std;
struct node{node *next;int where;
} *first[100001], a[100001];
int n, l, f[100001];
inline void makelist(int x,int y){//连一条从x到y的边a[++l].where = y;a[l].next = first[x];first[x] = &a[l];
}
inline void solve(int i){f[i] = 1;//一开始就自己一个人for (node *x = first[i]; x; x = x->next)//找所有的儿子{solve(x->where);f[i] += f[x->where];//加上儿子子树的个数}
}
int main()
{scanf("%d", &n);memset(first, 0, sizeof(first));l = 0;for (int i = 2; i <= n;i++){int x;scanf("%d", &x);makelist(x, i);}solve(1);//解决以1号点为子树的情况for (int i = 1; i <= n;i++)printf("%d ", f[i]);return 0;
}

bfs写法

#include <bits/stdc++.h>
#define ll long long
using namespace std;
struct node
{node *next;int where;
} *first[100001], a[100001];
int n, l, f[100001], c[100001];
inline void makelist(int x, int y)
{ // 连一条从x到y的边a[++l].where = y;a[l].next = first[x];first[x] = &a[l];
}
int main()
{scanf("%d", &n);memset(first, 0, sizeof(first));l = 0;for (int i = 2; i <= n; i++){int x;scanf("%d", &x);makelist(x, i);}c[1] = 1;//队列,找出bfs序for (int k = 1, l = 1; l <= k; l++){int m = c[l];//头元素for (node *x = first[m]; x; x = x->next)//下属点c[++k] = x->where;}for (int i = n; i; i--)//倒着算的,算到前面的时候后面都计算过了{int m = c[i];f[m] = 1;for (node *x = first[m]; x; x = x->next){f[m] += f[x->where];}}for (int i = 1; i <= n; i++)printf("%d ", f[i]);
}

没有上司的舞会

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

#include <bits/stdc++.h>
#define ll long long
using namespace std;
struct node
{node *next;int where;
} *first[100001], a[100001];
int n, l ,c[100001],v[100001];
ll f[100001][2];
inline void makelist(int x, int y)
{ // 连一条从x到y的边a[++l].where = y;a[l].next = first[x];first[x] = &a[l];
}
inline void solve(int i){f[i][1] = v[i];for (node *x = first[i]; x;x=x->next){solve(x->where);f[i][0] += max(f[x->where][0], f[x->where][1]);f[i][1] += f[x->where][0];}
}
int main()
{scanf("%d", &n);memset(first, 0, sizeof(first));l = 0;for (int i = 2; i <= n;i++){int x;scanf("%d", &x);makelist(x, i);}for (int i = 1; i <= n;i++)scanf("%d", &v[i]);solve(1);printf("%lld\n", max(f[1][0], f[1][1]));
}

背包

01背包

01背包问题
在这里插入图片描述
在这里插入图片描述

#include <bits/stdc++.h>
using namespace std;const int N=1e3+10;
int v[N],w[N];
int f[N][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=1;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];return 0;
}

在这里插入图片描述
看一下 f[i][j] 的计算公式: f [ i ] [ j ] = m a x ( A , B ) f[i][j] = max(A, B) f[i][j]=max(A,B)

只用到了 f [ i − 1 ] [ j ] f [ i − 1 ] [ j − v i ] f[i - 1][j]f[i-1][j - v_i] f[i1][j]f[i1][jvi] ,即只用到了 f [ i − 1 ] f[i - 1] f[i1] 这一层,并且用到的体积为 j j j j − v i j - v_i jvi,都是小于等于 j 的。

因此可以从体积为 V 开始,利用 f [ i − 1 ] f[i - 1] f[i1]的数据,求解出 f [ i ] [ j ] f[i][j] f[i][j],把 f [ i ] [ j ] f[i][j] f[i][j]放到 f [ i − 1 ] [ j ] f[i -1][j] f[i1][j] 的位置上。这样 f 数组就能优化到一维了。并且,当 背包容量小于 v j v_j vj 的时候, f [ i ] [ j ] f[i][j] f[i][j] = max{ f [ i − 1 ] [ j ] f[i - 1][j] f[i1][j],0 } = f [ i − 1 ] [ j ] f[i - 1][j] f[i1][j]。所以 j 只需要从 V 遍历到 v j v_j vj 即可。

#include <bits/stdc++.h>
using namespace std;using ll=long long;const int N=1e3+10;
int v[N],w[N];
int f[N];
int main()
{ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);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--){f[j]=max(f[j],f[j-v[i]]+w[i]);}}cout<<f[m];return 0;
}

完全背包

完全背包
思路1
思路2
在这里插入图片描述

在这里插入图片描述

#include<iostream>
using namespace std;
const int N = 1010;
int f[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++){for(int k = 0 ; k*v[i]<=j ; k++)f[i][j] = max(f[i][j],f[i-1][j-k*v[i]]+k*w[i]);}cout<<f[n][m]<<endl;return 0;
}

在这里插入图片描述
有了上面的关系,那么其实 k 循环可以不要了,核心代码优化成这样:

#include <bits/stdc++.h>
using namespace std;
const int N=1e3+10;
int v[N],w[N];
int f[N][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++){f[i][j]=f[i-1][j];if(j-v[i]>=0)f[i][j]=max(f[i-1][j],f[i][j-v[i]]+w[i]); }}cout<<f[n][m];return 0;
}
f[i][j] = max(f[i][j],f[i-1][j-v[i]]+w[i]);//01背包f[i][j] = max(f[i][j],f[i][j-v[i]]+w[i]);//完全背包问题//0 1 背包代码优化这里有详细说明

在这里插入图片描述

#include<iostream>
using namespace std;
const int N = 1010;
int f[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++){f[j] = max(f[j],f[j-v[i]]+w[i]);//注意了,这里的j是从小到大枚举,和01背包不一样}cout<<f[m]<<endl;
}

多重背包

多重背包
思路
在这里插入图片描述

#include <iostream>
#include <algorithm>using namespace std;
const int N = 110;int v[N], w[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 = 1; j <= m; j ++){//枚举体积for(int k = 0; k <= s[i]; k ++){if(j >=  k * v[i]){f[i][j] = max(f[i][j], f[i - 1][j - k * v[i]] + k * w[i]);}}}}cout << f[n][m] << endl;return 0;
}

分组背包

分组背包
在这里插入图片描述

在这里插入图片描述

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

因为只用到了第i-1列,所以可以仿照01背包的套路逆向枚举体积

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

相关文章:

动态规划基础

动态规划 动态规划概论楼梯最短路最长上升子序列&#xff08;LIS)最长公共子序列&#xff08;LCS)最长回文子串 概率动态规划区间动态规划石子合并括号序列石子合并&#xff08;环形&#xff09; 树形动态规划统计人数没有上司的舞会 背包01背包完全背包多重背包分组背包 动态规…...

导入 Excel 批量替换文件名称及扩展名

重命名的需求是多种多样的&#xff0c;我们一个方法或一个工具很难说完全满足 100% 的文件重命名的需求。如果我们的文件重命名的需求非常的复杂的时候&#xff0c;我们能否有一个万全的方法来帮我们实现呢&#xff1f;那今天就给大家介绍一下导入 excel 的方式批量修改文件名称…...

降低AIGC检测率的AI润色提示词模板

以下是针对降低AIGC检测率的 AI润色提示词模板&#xff0c;涵盖语言风格优化、逻辑重构、学术规范强化等维度&#xff0c;结合反检测策略设计&#xff0c;可直接用于DeepSeek等工具&#xff1a; 一、标题与摘要优化 1. 标题去AI化 提示词&#xff1a; 请将以下标题改写成更学…...

系统思考—提升解决动态性复杂问题能力

感谢合作伙伴的信任推荐&#xff01; 客户今年的人才发展重点之一&#xff0c;是提升管理者应对动态性、复杂性问题的能力。 在深入交流后&#xff0c;系统思考作为关键能力模块&#xff0c;最终被纳入轮训项目——这不仅是一次培训合作&#xff0c;更是一场共同认知的跃迁&am…...

spring--整合Mybatis详解

整合Mybatis 步骤&#xff1a; 1.导入相关Maven依赖 junit mybatis mysql数据库连接 spring相关的 aop织入 mybatis-spring 2.编写配置文件 3.测试 回忆mybatis 还需连接数据库 导入依赖&#xff1a; <dependencies><dependency><groupId>juni…...

深入理解 HTML5 Audio:网页音频播放的新时代

在网页开发领域,音频的嵌入和播放一直是一个重要且不断演进的话题。HTML5 的出现,为网页音频播放带来了标准化的解决方案,极大地改善了开发者和用户的体验。 一、HTML5 之前的音频播放状况 在 HTML5 诞生之前,互联网上缺乏统一的网页音频播放标准。当时,大多数音频播放依…...

Cloudflare 缓存工作原理

Cloudflare 缓存是 Cloudflare 内容分发网络&#xff08;CDN&#xff09;的一个关键组成部分&#xff0c;通过在靠近用户的全球网络边缘服务器上存储和交付内容&#xff0c;显著提升网站性能。以下是关于 Cloudflare 缓存的相关内容&#xff1a; 工作原理 内容请求&#xff1a…...

【Unity3D中UI与物体可见性的判断方法】

系列文章目录 unity知识点 文章目录 系列文章目录&#x1f449;前言&#x1f449;一、判断UI的可见性1-1、第一种1-2、通过RectTransform计算可视区域1-3、滚动容器内可见性检测&#xff08;Scroll View&#xff09; &#x1f449;二、判断物体的可见性2-1、视锥体检测方法2-2…...

日语学习-日语知识点小记-构建基础-JLPT-N4阶段(1):承上启下,继续上路

日语学习-日语知识点小记-构建基础-JLPT-N4阶段(1):承上启下,继续上路 1、前言(1)情况说明(2)工程师的信仰2、知识点(1)普通形(ふつうけい)と思います(2)辞書形ことができます(3)Vたことがあります。(4)Vた とき & Vる とき3、单词(1)日语单词(2…...

ubuntu24.04 cmake 报错 libldap-2.5.so.0 解决办法

apt cmake有毛病 换源重新安装 wget -O - https://apt.kitware.com/keys/kitware-archive-latest.asc 2>/dev/null | sudo apt-key add - sudo apt-add-repository "deb https://apt.kitware.com/ubuntu/ $(lsb_release -cs) main" sudo apt update sudo apt in…...

Mac 关闭浏览器左右滑动切换页面的问题

在使用触控板&#xff0c;操作浏览器时&#xff0c;左右滑动时&#xff0c;浏览器容易触发前进或者后退去查看历史记录。 如何关闭呢&#xff1f; 打开Mac- 系统设置-触控板 -更多手势 将轻扫切换页面设置为关&#xff0c;就可以了...

在 openEuler 24.03 (LTS) 操作系统上添加 ollama 作为系统服务的步骤

以下是在 openEuler 操作系统上添加 ollama 作为系统服务的步骤&#xff1a; 创建 systemd 服务文件 sudo vi /etc/systemd/system/ollama.service将以下内容写入服务文件&#xff08;按需修改参数&#xff09;&#xff1a; [Unit] DescriptionOllama Service Afternetwork.…...

华为昇腾服务器上查看固件、驱动和CANN版本的常用方法

Hey小伙伴们~&#x1f44b; 今天来聊聊怎么在华为昇腾服务器上查看固件、驱动和CANN版本吧&#xff01;&#x1f4bb; 这些信息对于确保你的服务器运行顺畅可是超级重要的哦&#xff01;下面就来给大家介绍几种常用的查看方法&#xff01;&#x1f447; &#x1f31f; ‌1. 查…...

击球手怎么玩·棒球1号位

以棒球运动为例&#xff0c;在棒球运动中&#xff0c;击球手&#xff08;Batter&#xff09;是进攻方的核心角色&#xff0c;负责通过击球创造得分机会。以下是结合棒球运动的详细介绍和击球技巧指南&#xff1a; 一、棒球基础规则 比赛目标 击球手需将投手&#xff08;Pitch…...

java基础多态------面试八股文

是什么是多态 类引用指向子类对象&#xff0c;并调用子类重写的方法&#xff0c;实现不同的行为 例子 class Animal {void sound() {System.out.println("动物发出声音");} }class Dog extends Animal {Overridevoid sound() {System.out.println("狗叫&…...

Python中的字典

文章目录 一、Python中的字典1. 字典的特点2. 字典的创建3. 字典的常见操作1. **访问字典中的值**2. **修改字典中的值**3. **添加键值对**4. **删除键值对**5. **检查键是否存在**6. **获取字典的长度**7. **遍历字典** 4. 字典的方法5. 嵌套字典6. 字典的优点7. 示例总结 二、…...

C++对象生命周期管理:从构造到析构的完整指南

在C开发中&#xff0c;准确掌握对象的生命周期管理是避免内存泄漏和资源竞争的关键。本文通过完整代码示例和内存布局分析&#xff0c;深入解析构造/析构顺序、继承体系、智能指针等核心机制&#xff0c;并分享实用调试技巧。 一、成员变量构造顺序&#xff1a;声明即命运 cl…...

代码随想录第14天:(二叉树)

一、找树左下角的值&#xff08;Leetcode 513&#xff09; 递归法&#xff1a; class Solution:def findBottomLeftValue(self, root: TreeNode) -> int:# 初始化最大深度为 -1&#xff0c;表示当前尚未遍历任何节点# 初始化 result 为 None&#xff0c;最终将存储最左边的…...

TCP/UDP的连接和数据发送过程详解

TCP TCP三次握手 在服务端启动好后会调用 listen() 方法&#xff0c;进入到 LISTEN 状态&#xff0c;然后静静等待客户端的连接请求到来。 而此时客户端主动调用 connect(IP地址) &#xff0c;就会向某个IP地址发起第一次握手&#xff0c;会先建立个半连接&#xff0c;发送SYN…...

2. 单词个数统计

【问题描述】 编写一个程序&#xff0c;输入一个句子&#xff0c;然后统计出这个句子当中不同的单词个数。例如&#xff0c;对于句子“one little two little three little boys"&#xff0c;总共有5个不同的单词&#xff0c;one, little, two, three, boys。 说明&…...

Js生成螺旋数组。

这段代码定义了一个名为 vetux 的函数&#xff0c;用于生成一个螺旋矩阵。螺旋矩阵是一种按照螺旋顺序填充数字的二维数组。以下是代码的详细解释&#xff1a; 函数定义 function vetux(n, m) {// 创建一个 m 行 n 列的二维数组&#xff0c;初始值为 0const a new Array(m).…...

《Vue.js组件化开发实战:从安全纵深到性能跃迁》

开篇&#xff1a;组件化开发的工业革命 当全球500强企业的核心业务系统在12.12大促中经受每秒38万次请求冲击时&#xff0c;我们突然意识到&#xff1a;现代前端组件已不再是简单的UI积木&#xff0c;而是承载业务逻辑、安全防护、性能优化的纳米级作战单元。本文将从军工级系统…...

【Git】--- 多人协作实战场景

Welcome to 9ilks Code World (๑•́ ₃ •̀๑) 个人主页: 9ilk (๑•́ ₃ •̀๑) 文章专栏&#xff1a; Git 前面我们学习了Git的所有本地仓库的相关操作:git基本操作,分支理解,版本回退,冲突解决等等。同时我们还理解了远端仓库在开发的作用以及相关操作push…...

SmolVLM2: The Smollest Video Model Ever(二)

这是对论文《SmolVLM: Redefining small and efficient multimodal models》的整理与翻译 SmolVLM&#xff1a;重新定义小型高效的多模态模型 拥抱脸、斯坦福大学 图1 小而强大&#xff1a;SmolVLM与其他最先进的小型视觉语言模型&#xff08;VLM&#xff09;的比较。图像结果来…...

如何通过前端表格控件实现自动化报表?1

背景 最近伙伴客户的项目经理遇见一个问题&#xff0c;他们在给甲方做自动化报表工具&#xff0c;项目已经基本做好了&#xff0c;但拿给最终甲方&#xff0c;业务人员不太买账&#xff0c;项目经理为此也是天天抓狂&#xff0c;没有想到合适的应对方案。 现阶段主要面临的问…...

数据库8(函数,变量)

1.数据类型 char(10):不足十个字符&#xff0c;用空格补全&#xff0c;数据定长&#xff1b;非统一字符编码&#xff0c;一个汉字要占两位char(2) nchar(10):不足十个字符&#xff0c;用空格补全&#xff0c;数据定长&#xff1b;统一字符编码&#xff0c;一个汉字占一位 nch…...

电阻式传感器(三)——电位器式传感器等效电路分析

(1)电位器式传感器的基本工作原理 将机械位移或其他可转换为位移变化的非电量转换为与其有一定函数关系的电阻变化&#xff0c;从而引起输出电压变化。 类型 基本结构 旋转型 直线型 非线性型 &#xff08;2&#xff09;电位器式传感器的等效电路分析 电位器式传感器的核…...

LangChain4j(1):初步认识Java 集成 LLM 的技术架构

LangChain 作为构建具备 LLM 能力应用的框架&#xff0c;虽在 Python 领域大放异彩&#xff0c;但 Java 开发者却只能望洋兴叹。LangChain4j 正是为解决这一困境而诞生&#xff0c;它旨在借助 LLM 的强大效能&#xff0c;增强 Java 应用&#xff0c;简化 LLM 功能在Java应用中的…...

力扣刷题——1339.分裂二叉树的最大乘积

给你一棵二叉树&#xff0c;它的根为 root 。请你删除 1 条边&#xff0c;使二叉树分裂成两棵子树&#xff0c;且它们子树和的乘积尽可能大。 由于答案可能会很大&#xff0c;请你将结果对 10^9 7 取模后再返回。 示例 1&#xff1a; 输入&#xff1a;root [1,2,3,4,5,6] 输…...

Pytest+Allure+Excel接口自动化测试框架实战

&#x1f345; 点击文末小卡片&#xff0c;免费获取软件测试全套资料&#xff0c;资料在手&#xff0c;涨薪更快 1. Allure 简介 简介 Allure 框架是一个灵活的、轻量级的、支持多语言的测试报告工具&#xff0c;它不仅以 Web 的方式展示了简介的测试结果&#xff0c;而且允…...

交易所开发全流程解析:KYC与U盾在安全合规中的战略价值

——2025年加密资产交易平台的技术架构与风控体系深度实践 一、交易所开发的核心技术架构与流程 1. 系统定位与合规基础 交易所开发需首先明确中心化&#xff08;CEX&#xff09;、去中心化&#xff08;DEX&#xff09;或混合架构的定位。中心化交易所&#xff08;如币安&…...

简单了解一下Unity的Resources.UnloadUnusedAssets

基本概念 Resources.UnloadUnusedAssets()是Unity提供的一个内存管理方法&#xff0c;用于卸载当前未被任何GameObject引用的资源&#xff0c;包括贴图、材质、网格、音频等资源。 在Unity中&#xff0c;资源在加载后会占用内存&#xff0c;而当这些资源不再被场景中的对象引…...

ECMAScript 7~10 新特性

ECMAScript 7 新特性 ECMAScript 6 新特性&#xff08;一&#xff09; ECMAScript 6 新特性&#xff08;二&#xff09; ECMAScript 7~10 新特性&#xff08;本文&#xff09; 1. 数组方法 Array.prototype.includes() 用来检测数组中是否包含指定元素&#xff0c;返回布尔值&…...

leetcode_1. 两数之和_java

1. 两数之和https://leetcode.cn/problems/two-sum/ 1、题目 给定一个整数数组 nums 和一个整数目标值 target&#xff0c;请你在该数组中找出 和为目标值 target 的那 两个 整数&#xff0c;并返回它们的数组下标。 你可以假设每种输入只会对应一个答案&#xff0c;并且你…...

Mysql索引(四)

1、B树&#xff1a;B树即平衡查找树&#xff0c;一般理解为平衡多路查找树&#xff0c;也称为B-树、B_树。是一种自平衡树状数据结构&#xff0c;能对存储的数据进行O(log n)的时间复杂度进行查找、插入和删除&#xff1b; 1&#xff09;每个节点占用一个盘块的磁盘空间&#x…...

力扣——【1991. 找到数组的中间位置】

#前缀和思想 主要利用递推的思想&#xff0c;将数列的前n&#xff01;项和存到一个新数列中&#xff0c;递推公式可能需要自己推导 一个数列的值等于另一个数列的第i个元素加上这一个数列的第i-1个元素 同时需要初始化这个数列的第一个元素另一个数列的第一个元素 #思路 本…...

在 Linux 系统(ubuntu/kylin)上安装 Docker

在 Linux 系统上安装 Docker 的步骤如下(以 Ubuntu/Debian 和 CentOS/RHEL 为例): 请用./check-config config检查内核是否支持,necessarily 必须全部enable。 以下是脚本自行复制运行: #!/usr/bin/env sh set -eEXITCODE=0# bits of this were adapted from lxc-checkco…...

【实证分析】数智化转型对制造企业全要素生产率的影响及机制探究(1999-2023年)

数智化转型是实现数字经济与实体经济深度融合,推动制造企业高质量可持续发展的必然选择,也是加快新质生产力发展的重要抓手。参照宋冬林&#xff08;2025&#xff09;的做法&#xff0c;对来自科技进步与对策《数智化转型对制造企业全要素生产率的影响及机制探究——基于中国制…...

lower_bound

在C中&#xff0c;lower_bound 返回的是一个迭代器&#xff08;iterator&#xff09;&#xff0c;而不是直接的下标位置。因此&#xff0c;为了得到数组中的索引&#xff08;即 pos1&#xff09;&#xff0c;你需要用返回的迭代器减去数组的起始地址&#xff08;num&#xff09…...

biblatex 的 Biber 警告​​:tex文件运行无法生成参考文献和目录

原因​​&#xff1a;使用了 biblatex 管理参考文献&#xff0c;但未运行 biber 生成参考文献数据。 ​​解决​​&#xff1a;更新 LaTeX Workshop 配置 修改你的 settings.json&#xff0c;添加 biber 工具并更新编译流程&#xff1a; {"latex-workshop.latex.tools&…...

解锁 MCP:模型上下文协议的介绍与应用​,技术解析与应用场景

欢迎来到涛涛聊AI,这几天MCP很火,咱们一起学习下吧。 一、什么是 MCP MCP,即 Model Context Protocol(模型上下文协议),是由 Anthropic 推出的一个具有创新性的开放协议 。它的核心目标是统一 LLM 应用与外部数据源和工具之间的通信方式,为 AI 开发打造标准化的上下文…...

十二种存储器综合对比——《器件手册--存储器》

存储器 名称 特点 用途 EEPROM 可电擦除可编程只读存储器&#xff0c;支持按字节擦除和写入操作&#xff0c;具有非易失性&#xff0c;断电后数据不丢失。 常用于存储少量需要频繁更新的数据&#xff0c;如设备配置参数、用户设置等。 NOR FLASH 支持按字节随机访问&…...

对重大保险风险测试的算法理解

今天与同事聊到重大保险风险测试&#xff0c;借助下面链接的文章&#xff0c; 谈IFRS 17下的重大保险风险测试 - 知乎 谈一下对下图这个公式的理解。 尤其是当看到下面这段文字的解释时&#xff0c;感觉有些算法上的东西&#xff0c;需要再澄清一些。 首先&#xff0c;上面文…...

App Cleaner Pro for Mac 中 Mac软件卸载工具

App Cleaner Pro for Mac 中 Mac软件卸载工具 一、介绍 App Cleaner & Uninstaller Pro Mac破解&#xff0c;是一款Mac软件卸载工具&#xff0c;残余垃圾清除工具&#xff01;可以卸载应用程序或只删除不需要的服务文件&#xff0c;甚至可以删除以前删除的应用程序中的文…...

【操作系统】线程同步:原理、方法与实践

一、线程同步的核心概念 1.1 为什么需要线程同步&#xff1f; 在多线程环境中&#xff0c;当多个线程并发访问共享资源&#xff08;如内存、文件、数据库等&#xff09;时&#xff0c;可能会引发数据竞争&#xff08;Race Condition&#xff09;&#xff0c;导致数据不一致或…...

vue实现二维码生成器和解码器

vue实现二维码生成器和解码器 1.生成基本二维码&#xff1a;根据输入的value生成二维码。 2.可定制尺寸&#xff1a;通过size调整大小。 3.颜色和背景色&#xff1a;设置二维码颜色和背景。 4.静区&#xff08;quiet zone&#xff09;支持&#xff1a;通过quietZone调整周围的…...

p2p的发展

PCDN&#xff08;P2P内容分发网络&#xff09;行业目前处于快速发展阶段&#xff0c;面临机遇与挑战并存的局面。 一、发展机遇 技术融合推动 边缘计算与5G普及&#xff1a;5G的高带宽、低延迟特性与边缘计算技术结合&#xff0c;显著提升PCDN性能&#xff0c;降低延迟&#x…...

DeepSeek提示词实战大全:提示词合集和使用技巧

大家好,我是大 F,深耕AI算法十余年,互联网大厂技术岗。 知行合一,不写水文,喜欢可关注,分享AI算法干货、技术心得。 更多文章可关注《大模型理论和实战》、《DeepSeek技术解析和实战》,一起探索技术的无限可能! 【数据集篇】更多阅读: 大语言模型常见任务及评测数据集…...

23种设计模式生活化场景,帮助理解

以下是 23种设计模式的生活化场景 及其核心对比&#xff0c;通过日常例子和比喻帮助理解它们的本质区别和应用场景&#xff1a; 创建型模式&#xff08;5种&#xff09; 1. 工厂方法&#xff08;Factory Method&#xff09; • 场景&#xff1a;快餐店的点餐系统。 • 问题&a…...

Kotlin 学习-方法和参数类型

/*** kotlin 的方法有三种* */fun main() {/*** 方法一* 1.普通类的成员方法申明与调用* &#xff08;1&#xff09;需要先构建出实例对象&#xff0c;才能访问成员方法* &#xff08;2&#xff09;实例对象的构建只需要在类名后面加上()* */Person().test()/*** 方法二&#x…...