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

动态规划dp

这里写目录标题

  • 动态规划
    • 01背包
    • 完全背包
    • 多重背包
    • 混合背包
    • 二维费用的背包
    • 分组背包
    • 有依赖的背包
    • 背包问题求方案数
    • 背包问题求具体方案
    • 数位 DP
    • 状压 DP
    • 常用例题

动态规划

01背包

n n n 件物品和一个容量为 W W W 的背包,第 i i i 件物品的体积为 w [ i ] w[i] w[i],价值为 v [ i ] v[i] v[i],求解将哪些物品装入背包中使总价值最大。

思路:

我们设 d p [ i ] [ j ] dp[i][j] dp[i][j]表示前 i i i件物品放入一个容量为 j j j的背包可以获得的最大价值

如果不放第 i i i件物品,那么问题就变成了前 i − 1 i-1 i1件物品放入一个容量为 j j j的背包可以获得的最大价值即 d p [ i − 1 ] [ j ] dp[i-1][j] dp[i1][j]

如果放第 i i i件物品,那么问题变成了前 i − 1 i-1 i1件物品放入一个容量为 j − w [ i ] j-w[i] jw[i]的背包可以获得的最大价值,即当前可以获得最大价值为

i − 1 i-1 i1件物品放入一个容量为 j − w [ i ] j-w[i] jw[i]的背包加上当前第 i i i件物品放入背包的价值,即 d p [ i − 1 ] [ j − w [ i ] ] + v [ i ] dp[i-1][j-w[i]]+v[i] dp[i1][jw[i]]+v[i]

于是我们可以得到 转移方程 d p [ i ] [ j ] = m a x ( d p [ i − 1 ] [ j ] , d p [ i − 1 ] [ j − w [ i ] ] + v [ i ] ) dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - w[i]] + v[i]) dp[i][j]=max(dp[i1][j],dp[i1][jw[i]]+v[i])

for (int i = 1; i <= n; i++)for (int j = 0; j <= W; j++){dp[i][j] = dp[i - 1][j]; // 不选第 i 个物品if (j >= w[i]) // 如果当前容量 j 能装下物品 idp[i][j] = max(dp[i][j], dp[i - 1][j - w[i]] + v[i]); // 不选第i个物品和选了第i个物品取最大值}

我们可以发现,第 i i i 个物品的状态是由第 i − 1 i - 1 i1 个物品转移过来的,每次的 j j j 转移过来后,第 i − 1 i - 1 i1 个方程的 j j j 已经没用了,于是我们想到可以把二维方程压缩成 一维 的,用以 优化空间复杂度

for (int i = 1; i <= n; i++)  //当前装第 i 件物品for (int j = W; j >= w[i]; j--)  //背包容量为 jdp[j] = max(dp[j], dp[j - w[i]] + v[i]);  //判断背包容量为 j 的情况下能是实现总价值最大是多少

完全背包

n n n 件物品和一个容量为 W W W 的背包,第 i i i 件物品的体积为 w [ i ] w[i] w[i],价值为 v [ i ] v[i] v[i],每件物品有无限个,求解将哪些物品装入背包中使总价值最大。

思路:

思路和01背包差不多,但是每一件物品有无限个,其实就是从每 物品中取 $0, 1, 2,… $ 件物品加入背包中

for (int i = 1; i <= n; i++)for (int j = 0; j <= W; j++)for (int k = 0; k * w[i] <= j; k++)    //选取几个物品 dp[i][j] = max(dp[i][j], dp[i - 1][j - k * w[i]] + k * v[i]);

实际上,我们可以发现,取 k k k 件物品可以从取 k − 1 k - 1 k1 件转移过来,那么我们就可以将 k k k 的循环优化掉

for (int i = 1; i <= n; i++)for (int j = 0; j <= W; j++){dp[i][j] = dp[i - 1][j];if (j >= w[i])dp[i][j] = max(dp[i][j], dp[i][j - w[i]] + v[i]);}

和 01 背包 类似地压缩成一维

for (int i = 1; i <= n; i++)for (int j = w[i]; j <= W; j++)dp[j] = max(dp[j], dp[j - w[i]] + v[i]);

多重背包

n n n 物品和一个容量为 W W W 的背包,第 i i i 物品的体积为 w [ i ] w[i] w[i],价值为 v [ i ] v[i] v[i],数量为 s [ i ] s[i] s[i],求解将哪些物品装入背包中使总价值最大。

思路:

对于每一种物品,都有 s [ i ] s[i] s[i] 种取法,我们可以将其转化为01背包问题

for (int i = 1; i <= n; i++){for (int j = W; j >= 0; j--)for (int k = 0; k <= s[i]; k++){if (j - k * w[i] < 0) break;dp[j] = max(dp[j], dp[j - k * w[i]] + k * v[i]);}

上述方法的时间复杂度为 O ( n ∗ m ∗ s ) O(n * m * s) O(nms)

for (int i = 1; i <= n; i++){scanf("%lld%lld%lld", &x, &y, &s);  //x 为体积, y 为价值, s 为数量t = 1;while (s >= t){w[++num] = x * t;v[num] = y * t;s -= t;t *= 2;}w[++num] = x * s;v[num] = y * s;
}
for (int i = 1; i <= num; i++)for (int j = W; j >= w[i]; j--)dp[j] = max(dp[j], dp[j - w[i]] + v[i]);

尽管采用了 二进制优化,时间复杂度还是太高,采用 单调队列优化,将时间复杂度优化至 O ( n ∗ m ) O(n * m) O(nm)

#include <bits/stdc++.h>
using namespace std;
const int N = 2e5 + 10;
int n, W, w, v, s, f[N], g[N], q[N];
int main(){ios::sync_with_stdio(false);cin.tie(0);cin >> n >> W;for (int i = 0; i < n; i ++ ){memcpy ( g, f, sizeof f);cin >> w >> v >> s;for (int j = 0; j < w; j ++ ){int head = 0, tail = -1;for (int k = j; k <= W; k += w){if ( head <= tail && k - s * w > q[head] ) head ++ ;//保证队列长度 <= s while ( head <= tail && g[q[tail]] - (q[tail] - j) / w * v <= g[k] - (k - j) / w * v ) tail -- ;//保证队列单调递减 q[ ++ tail] = k;f[k] = g[q[head]] + (k - q[head]) / w * v;}}}cout << f[W] << "\n";return 0;
}

混合背包

放入背包的物品可能只有 1 件(01背包),也可能有无限件(完全背包),也可能只有可数的几件(多重背包)。

思路:

分类讨论即可,哪一类就用哪种方法去 d p dp dp

#include <bits/stdc++.h>using namespace std;int n, W, w, v, s;int main(){cin >> n >> W;vector <int> f(W + 1);for (int i = 0; i < n; i ++ ){cin >> w >> v >> s;if (s == -1){for (int j = W; j >= w; j -- )f[j] = max(f[j], f[j - w] + v);}else if (s == 0){for (int j = w; j <= W; j ++ )f[j] = max(f[j], f[j - w] + v);}else {int t = 1, cnt = 0;vector <int> x(s + 1), y(s + 1);while (s >= t){x[++cnt] = w * t;y[cnt] = v * t;s -= t;t *= 2;}x[++cnt] = w * s;y[cnt] = v * s;for (int i = 1; i <= cnt; i ++ )for (int j = W; j >= x[i]; j -- )f[j] = max(f[j], f[j - x[i]] + y[i]);}}cout << f[W] << "\n";return 0;
}

二维费用的背包

n n n 件物品和一个容量为 W W W 的背包,背包能承受的最大重量为 M M M,每件物品只能用一次,第 i i i 件物品的体积是 w [ i ] w[i] w[i],重量为 m [ i ] m[i] m[i],价值为 v [ i ] v[i] v[i],求解将哪些物品放入背包中使总体积不超过背包容量,总重量不超过背包最大容量,且总价值最大。

思路:

背包的限制条件由一个变成两个,那么我们的循环再多一维即可。

for (int i = 1; i <= n; i++)for (int j = W; j >= w; j--)  //容量限制for (int k = M; k >= m; k--)  //重量限制dp[j][k] = max(dp[j][k], dp[j - w][k - m] + v);

分组背包

n n n 物品,一个容量为 W W W 的背包,每组物品有若干,同一组的物品最多选一个,第 i i i 组第 j j j 件物品的体积为 w [ i ] [ j ] w[i][j] w[i][j],价值为 v [ i ] [ j ] v[i][j] v[i][j],求解将哪些物品装入背包,可使物品总体积不超过背包容量,且使总价值最大。

思路:

考虑每中的某件物品选不选,可以选的话,去下一组选下一个,否则在这组继续寻找可以选的物品,当这组遍历完后,去下一组寻找。

#include <bits/stdc++.h>
using namespace std;
const int N = 110;
int n, W, s[N], w[N][N], v[N][N], dp[N];
int main(){cin >> n >> W;for (int i = 1; i <= n; i++){scanf("%d", &s[i]);for (int j = 1; j <= s[i]; j++)scanf("%d %d", &w[i][j], &v[i][j]);}for (int i = 1; i <= n; i++)for (int j = W; j >= 0; j--)for (int k = 1; k <= s[i]; k++)if (j - w[i][k] >= 0)dp[j] = max(dp[j], dp[j - w[i][k]] + v[i][k]);cout << dp[W] << "\n";return 0;
}

有依赖的背包

n n n 个物品和一个容量为 W W W 的背包,物品之间有依赖关系,且之间的依赖关系组成一颗 的形状,如果选择一个物品,则必须选择它的 父节点,第 i i i 件物品的体积是 w [ i ] w[i] w[i],价值为 v [ i ] v[i] v[i],依赖的父节点的编号为 p [ i ] p[i] p[i],若 p [ i ] p[i] p[i] 等于 -1,则为 根节点。求将哪些物品装入背包中,使总体积不超过总容量,且总价值最大。

思路:

定义 f [ i ] [ j ] f[i][j] f[i][j] 为以第 i i i 个节点为根,容量为 j j j 的背包的最大价值。那么结果就是 f [ r o o t ] [ W ] f[root][W] f[root][W],为了知道根节点的最大价值,得通过其子节点来更新。所以采用递归的方式。
对于每一个点,先将这个节点装入背包,然后找到剩余容量可以实现的最大价值,最后更新父节点的最大价值即可。

#include <bits/stdc++.h>
using namespace std;
const int N = 110;
int n, W, w[N], v[N], p, f[N][N], root;
vector <int> g[N];
void dfs(int u){for (int i = w[u]; i <= W; i ++ )f[u][i] = v[u];for (auto v : g[u]){dfs(v);for (int j = W; j >= w[u]; j -- )for (int k = 0; k <= j - w[u]; k ++ )f[u][j] = max(f[u][j], f[u][j - k] + f[v][k]);}
}
int main(){cin >> n >> W;for (int i = 1; i <= n; i ++ ){cin >> w[i] >> v[i] >> p;if (p == -1) root = i;else g[p].push_back(i);}dfs(root);cout << f[root][W] << "\n";return 0;
}

背包问题求方案数

n n n 件物品和一个容量为 W W W 的背包,每件物品只能用一次,第 i i i 件物品的重量为 w [ i ] w[i] w[i],价值为 v [ i ] v[i] v[i],求解将哪些物品放入背包使总重量不超过背包容量,且总价值最大,输出 最优选法的方案数,答案可能很大,输出答案模 10 9 + 7 10^9 + 7 109+7 的结果。

思路:

开一个储存方案数的数组 c n t cnt cnt c n t [ i ] cnt[i] cnt[i] 表示容量为 i i i 时的 方案数,先将 c n t cnt cnt 的每一个值都初始化为 1,因为 不装任何东西就是一种方案,如果装入这件物品使总的价值 更大,那么装入后的方案数 等于 装之前的方案数,如果装入后总价值 相等,那么方案数就是 二者之和

#include <bits/stdc++.h>
using namespace std;
#define LL long long
const int mod = 1e9 + 7, N = 1010;
LL n, W, cnt[N], f[N], w, v;
int main(){cin >> n >> W;for (int i = 0; i <= W; i ++ )cnt[i] = 1;for (int i = 0; i < n; i ++ ){cin >> w >> v;for (int j = W; j >= w; j -- )if (f[j] < f[j - w] + v){f[j] = f[j - w] + v;cnt[j] = cnt[j - w];}else if (f[j] == f[j - w] + v){cnt[j] = (cnt[j] + cnt[j - w]) % mod;}}cout << cnt[W] << "\n";return 0;
}

背包问题求具体方案

signed main() {int Task = 1;for (cin >> Task; Task; Task--) {int n, m;cin >> n >> m;vector<int> w(n + 1), v(n + 1);vector<vector<int>> dp(n + 2, vector<int>(m + 2));for (int i = 1; i <= n; i++) {cin >> w[i] >> v[i];}for (int i = n; i >= 1; i--) {for (int j = 0; j <= m; j++) {dp[i][j] = dp[i + 1][j];if (j >= w[i]) {dp[i][j] = max(dp[i][j], dp[i + 1][j - w[i]] + v[i]);}}}vector<int> ans;for (int i = 1; i <= n; i++) {if (m - w[i] >= 0 && dp[i][m] == dp[i + 1][m - w[i]] + v[i]) {ans.push_back(i);// cout << i << " ";m -= w[i];}}cout << ans.size() << "\n";for (auto i : ans) {cout << i << " ";}cout << "\n";}
}

数位 DP

/* pos 表示当前枚举到第几位
sum 表示 d 出现的次数
limit 为 1 表示枚举的数字有限制
zero 为 1 表示有前导 0
d 表示要计算出现次数的数 */
const int N = 15;
LL dp[N][N];
int num[N];
LL dfs(int pos, LL sum, int limit, int zero, int d) {if (pos == 0) return sum;if (!limit && !zero && dp[pos][sum] != -1) return dp[pos][sum];LL ans = 0;int up = (limit ? num[pos] : 9);for (int i = 0; i <= up; i++) {ans += dfs(pos - 1, sum + ((!zero || i) && (i == d)), limit && (i == num[pos]),zero && (i == 0), d);}if (!limit && !zero) dp[pos][sum] = ans;return ans;
}
LL solve(LL x, int d) {memset(dp, -1, sizeof dp);int len = 0;while (x) {num[++len] = x % 10;x /= 10;}return dfs(len, 0, 1, 1, d);
}

状压 DP

**题意:**在 n ∗ n n * n nn 的棋盘里面放 k k k 个国王,使他们互不攻击,共有多少种摆放方案。国王能攻击到它上下左右,以及左上左下右上右下八个方向上附近的各一个格子,共8个格子。

#include <bits/stdc++.h>
using namespace std;
#define LL long long
const int N = 15, M = 150, K = 1500;
LL n, k;
LL cnt[K];    //每个状态的二进制中 1 的数量
LL tot;    //合法状态的数量
LL st[K];    //合法的状态
LL dp[N][M][K];    //第 i 行,放置了 j 个国王,状态为 k 的方案数
int main(){ios::sync_with_stdio(false);cin.tie(0);cin >> n >> k;for (int s = 0; s < (1 << n); s ++ ){  //找出合法状态LL sum = 0, t = s;while(t){  //计算 1 的数量sum += (t & 1);t >>= 1;}cnt[s] = sum;if ( (( (s << 1) | (s >> 1) ) & s) == 0 ){  //判断合法性st[ ++ tot] = s;}}dp[0][0][0] = 1;for (int i = 1; i <= n + 1; i ++ ){for (int j1 = 1; j1 <= tot; j1 ++ ){    //当前的状态LL s1 = st[j1];for (int j2 = 1; j2 <= tot; j2 ++ ){    //上一行的状态LL s2 = st[j2];if ( ( (s2 | (s2 << 1) | (s2 >> 1)) & s1 ) == 0 ){for (int j = 0; j <= k; j ++ ){if (j - cnt[s1] >= 0)dp[i][j][s1] += dp[i - 1][j - cnt[s1]][s2];}}}}}cout << dp[n + 1][k][0] << "\n";return 0;
}

常用例题

题意:在一篇文章(包含大小写英文字母、数字、和空白字符(制表/空格/回车))中寻找 h e l l o w o r l d {\tt helloworld} helloworld(任意一个字母的大小写都行)的子序列出现了多少次,输出结果对 10 9 + 7 10^9+7 109+7 的余数。

字符串 DP ,构建一个二维 DP 数组, d p [ i ] [ j ] dp[i][j] dp[i][j] i i i 表示文章中的第几个字符, j j j 表示寻找的字符串的第几个字符,当字符串中的字符和文章中的字符相同时,即找到符合条件的字符, dp[i][j] = dp[i - 1][j] + dp[i - 1][j - 1] ,因为字符串中的每个字符不会对后面的结果产生影响,所以 DP 方程可以优化成一维的, 由于字符串中有重复的字符,所以比较时应该从后往前。

#include <bits/stdc++.h>
using namespace std;
#define LL long long
const int mod = 1e9 + 7;
char c, s[20] = "!helloworld";
LL dp[20];
int main(){dp[0] = 1;while ((c = getchar()) != EOF)for (int i = 10; i >= 1; i--)if (c == s[i] || c == s[i] - 32)dp[i] = (dp[i] + dp[i - 1]) % mod;cout << dp[10] << "\n";return 0;
}

题意:(最长括号匹配)给一个只包含‘(’,‘)’,‘[’,‘]’的非空字符串,“()”和“[]”是匹配的,寻找字符串中最长的括号匹配的子串,若有两串长度相同,输出靠前的一串。

设给定的字符串为 s \tt{}s s ,可以定义数组 d p [ i ] , d p [ i ] dp[i], dp[i] dp[i],dp[i] 表示以 s [ i ] s[i] s[i] 结尾的字符串里最长的括号匹配的字符。显然,从 i − d p [ i ] + 1 i - dp[i] + 1 idp[i]+1 i i i 的字符串是括号匹配的,当找到一个字符是‘)’或‘]’时,再去判断第 i − 1 − d p [ i − 1 ] i - 1 - dp[i - 1] i1dp[i1] 的字符和第 i i i 位的字符是否匹配,如果是,那么 dp[i] = dp[i - 1] + 2 + dp[i - 2 - dp[i - 1]]

#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e6 + 10;
string s;
int len, dp[maxn], ans, id;
int main(){cin >> s;len = s.length();for (int i = 1; i < len; i++){if ((s[i] == ')' && s[i - 1 - dp[i - 1]] == '(' ) || (s[i] == ']' && s[i - 1 - dp[i - 1]] == '[')){dp[i] = dp[i - 1] + 2 + dp[i - 2 - dp[i - 1]];if (dp[i] > ans) {ans = dp[i];  //记录长度id = i;  //记录位置}}}for (int i = id - ans + 1; i <= id; i++)cout << s[i];cout << "\n";return 0;
}

题意:去掉区间内包含“4”和“62”的数字,输出剩余的数字个数

int T,n,m,len,a[20];//a数组用于判断每一位能取到的最大值
ll l,r,dp[20][15];
ll dfs(int pos,int pre,int limit){//记搜//pos搜到的位置,pre前一位数//limit判断是否有最高位限制if(pos>len) return 1;//剪枝if(dp[pos][pre]!=-1 && !limit) return dp[pos][pre];//记录当前值ll ret=0;//暂时记录当前方案数int res=limit?a[len-pos+1]:9;//res当前位能取到的最大值for(int i=0;i<=res;i++)if(!(i==4 || (pre==6 && i==2)))ret+=dfs(pos+1,i,i==res&&limit);if(!limit) dp[pos][pre]=ret;//当前状态方案数记录return ret;
}
ll part(ll x){//把数按位拆分len=0;while(x) a[++len]=x%10,x/=10;memset(dp,-1,sizeof dp);//初始化-1(因为有可能某些情况下的方案数是0)return dfs(1,0,1);//进入记搜
}
int main(){cin>>n;while(n--){cin>>l>>r;if(l==0 && r==0)break;if(l) printf("%lld\n",part(r)-part(l-1));//[l,r](l!=0)else printf("%lld\n",part(r)-part(l));//从0开始要特判}
}

相关文章:

  • 力扣刷题DAY16(二叉树+迭代遍历)
  • NHANES指标推荐:PHDI
  • 数据库blog6_商业数据库下载知识
  • Day 34
  • 【强化学习】#7 基于表格型方法的规划和学习
  • 续位值运算---左移、右移
  • 2025年安克创新Anker社招校招入职测评 | 3天备考、自适应能力cata测评北森题库、安克创造者启航试炼、安克AI能力测评能力测评历年真题
  • 抖音出品AI短剧《牧野诡事》能否给AI短剧带来新一轮爆发?
  • Linux中的nfs
  • Linux(6)——第一个小程序(进度条)
  • python打卡day35@浙大疏锦行
  • ping命令常用参数以及traceout命令
  • Cookie 与 Session
  • 25. 日志装饰器的开发
  • springboot 多模块,打包为一个jar包
  • 细胞冻存的注意事项,细胞冻存试剂有哪些品牌推荐
  • day25JS- es5面向对象、Proxy代理对象
  • 【大模型报错解决】cublasLt ran into an error!
  • CSS定位详解:掌握布局的核心技术
  • Panasonic Programming Contest 2025(AtCoder Beginner Contest 406)D-E 题解
  • 企业信用网查询/东莞seo网站排名优化公司
  • 怎么给网站做绿标/品牌推广的意义
  • 信阳市住房和城乡建设厅网站/爱站网使用体验
  • 中小企业做网站推广/域名注册官网免费
  • 机械加工网入网/网站seo报价
  • 网页设计视频网站建设/app拉新一手渠道