算法题练习(第四周)
金明的预算方案【NOIP2006提高组】
分析
该问题属于带有依赖关系的背包问题,主件和附件的购买必须满足先决条件。每个主件最多有两个附件,购买附件前必须先购买对应的主件。我们需要在总预算内最大化物品的价值总和(价格乘以重要度)。
步骤分析:
-
数据存储: 将每个主件及其附件的信息分别存储。主件的价格和重要度存储在数组
v[i][0]和p[i][0]中,附件存储在对应的v[i][1]、p[i][1]和v[i][2]、p[i][2]中。 -
动态规划: 使用二维数组
f[i][j]表示前i个物品在预算j时的最大价值。对于每个主件,考虑四种购买组合:仅主件、主件 + 附件1、主件 + 附件2、主件 + 两个附件。更新动态规划数组时,取这些组合中的最大值。 -
转移方程: 对于每个主件
i,遍历所有可能的预算j,检查四种组合是否可行,并更新最大价值。
代码
#include <bits/stdc++.h>
using namespace std;
int n, m, v1, p1, q1;
int v[65][3], p[65][3]; // 主件i的附件价格和重要度
int f[65][32005]; // DP数组
int main() {
cin >> n >> m;
for (int i = 1; i <= m; i++) {
cin >> v1 >> p1 >> q1;
if (q1) { // 附件
// 找到对应的主件q1,将附件添加到其列表中
if (v[q1][1]) { // 如果第一个附件位置已占,放第二个
v[q1][2] = v1;
p[q1][2] = p1;
} else { // 否则放第一个
v[q1][1] = v1;
p[q1][1] = p1;
}
} else { // 主件
v[i][0] = v1;
p[i][0] = p1;
}
}
// 动态规划处理
for (int i = 1; i <= m; i++) { // 遍历所有物品,视为可能的主件
for (int j = 0; j <= n; j++) {
f[i][j] = f[i-1][j]; // 不选当前主件i
// 检查四种组合是否可行,并更新最大值
if (v[i][0] <= j) { // 仅主件
f[i][j] = max(f[i][j], f[i-1][j - v[i][0]] + v[i][0] * p[i][0]);
}
if (v[i][0] + v[i][1] <= j) { // 主件+附件1
f[i][j] = max(f[i][j], f[i-1][j - v[i][0] - v[i][1]]
+ v[i][0] * p[i][0] + v[i][1] * p[i][1]);
}
if (v[i][0] + v[i][2] <= j) { // 主件+附件2
f[i][j] = max(f[i][j], f[i-1][j - v[i][0] - v[i][2]]
+ v[i][0] * p[i][0] + v[i][2] * p[i][2]);
}
if (v[i][0] + v[i][1] + v[i][2] <= j) { // 主件+两个附件
f[i][j] = max(f[i][j], f[i-1][j - v[i][0] - v[i][1] - v[i][2]]
+ v[i][0] * p[i][0] + v[i][1] * p[i][1] + v[i][2] * p[i][2]);
}
}
}
cout << f[m][n]; // 输出最大价值
return 0;
}
解释
-
输入处理: 读取物品信息,将主件存入对应位置,附件添加到所属主件的附件列表中。
-
动态规划初始化: 初始时没有购买任何物品,价值为
0。 -
状态转移: 对于每个物品
i(视为可能的主件),遍历预算j,尝试四种购买组合,更新最大价值。 -
结果输出: 最终结果为处理完所有物品后的最大价值
f[m][n]。
总结
该解法通过动态规划处理主件及其附件的组合购买情况,确保在预算内获得最大价值。每个主件的四种购买组合被逐一检查并更新最优解,最终结果正确且高效。
传纸条【NOIP2008提高组】
标签
动态规划DP 费用流
分析
-
状态表示:使用三维数组
F[k][i][j]表示两个人同时走到第k步时,第一个人的纵坐标为i,第二个人的纵坐标为j时的最大好感度之和。这里k表示两个人的横纵坐标之和,即x1 + y1 = x2 + y2 = k。 -
状态转移:每一步两个人可以分别从左边或上边移动过来,共有四种转移方式。我们需要考虑上一步所有可能的合法状态,取最大值加上当前两个点的好感度。
-
避免重复:通过限制
i < j来确保两个人不会走到同一个位置。
代码
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 55;
int a[MAXN][MAXN];
int F[2 * MAXN][MAXN][MAXN];
int main() {
int m, n;
scanf("%d%d", &m, &n);
for (int i = 1; i <= m; i++)
for (int j = 1; j <= n; j++)
scanf("%d", &a[i][j]);
memset(F, -1, sizeof(F));
F[2][1][1] = 0;
for (int k = 3; k < m + n; k++)
for (int i = 1; i < n; i++)
for (int j = i + 1; j <= n; j++) {
int s = -1;
if (F[k-1][i][j] > s) s = F[k-1][i][j];
if (i > 1 && F[k-1][i-1][j] > s) s = F[k-1][i-1][j];
if (j > 1 && F[k-1][i][j-1] > s) s = F[k-1][i][j-1];
if (i > 1 && j > 1 && F[k-1][i-1][j-1] > s) s = F[k-1][i-1][j-1];
if (s == -1) continue;
int x1 = k - i, y1 = i;
int x2 = k - j, y2 = j;
if (x1 > m || x2 > m) continue;
F[k][i][j] = s + a[x1][y1] + a[x2][y2];
}
printf("%d", F[m + n - 1][n - 1][n]);
return 0;
}
解释
-
输入处理:读取矩阵的行数
m和列数n,以及矩阵中的每个元素。 -
初始化:将动态规划数组
F初始化为-1,表示不可达状态。初始状态F[2][1][1] = 0表示两个人在起点时的好感度和为0。 -
动态规划转移:遍历每个可能的步数
k,以及每个可能的纵坐标i和j,确保i < j。计算当前状态的最大值,并更新当前状态。 -
结果输出:输出
F[m + n][n][n + 1],即两个人在终点的前一步时的最大好感度之和。如果该状态不可达,输出0。
总结
这种方法通过动态规划有效避免了路径重叠的问题,并确保了路径的最大好感度之和。总的来说是个不错的动态规划模板。
金明的预算方案【NOIP2006提高组】
标签
动态规划DP 背包DP
分析
该问题属于依赖背包问题,需要处理主件和附件的依赖关系。每个主件最多有两个附件,且附件必须在其主件被购买的情况下才能选择。解决思路是将每个主件及其附件视为一个组合,转化为分组背包问题,每组有四种可能的购买方式:仅主件、主件+附件1、主件+附件2、主件+附件1+附件2。通过动态规划枚举所有可能组合,找到总价值最大的方案。
代码
#include <bits/stdc++.h>
using namespace std;
int main() {
int n, m;
cin >> n >> m;
vector<vector<int>> v(m + 1, vector<int>(3, 0));
vector<vector<int>> p(m + 1, vector<int>(3, 0));
vector<vector<int>> dp(m + 1, vector<int>(n + 1, 0));
for (int i = 1; i <= m; ++i) {
int v1, p1, q;
cin >> v1 >> p1 >> q;
if (q == 0) {
v[i][0] = v1;
p[i][0] = p1;
} else {
if (v[q][1] == 0) {
v[q][1] = v1;
p[q][1] = p1;
} else {
v[q][2] = v1;
p[q][2] = p1;
}
}
}
for (int i = 1; i <= m; ++i) {
for (int j = 0; j <= n; ++j) {
dp[i][j] = dp[i - 1][j];
if (v[i][0] == 0) continue;
if (j >= v[i][0]) {
dp[i][j] = max(dp[i][j], dp[i - 1][j - v[i][0]] + v[i][0] * p[i][0]);
}
if (v[i][1] != 0 && j >= v[i][0] + v[i][1]) {
int cost = v[i][0] + v[i][1];
dp[i][j] = max(dp[i][j], dp[i - 1][j - cost] + v[i][0] * p[i][0] + v[i][1] * p[i][1]);
}
if (v[i][2] != 0 && j >= v[i][0] + v[i][2]) {
int cost = v[i][0] + v[i][2];
dp[i][j] = max(dp[i][j], dp[i - 1][j - cost] + v[i][0] * p[i][0] + v[i][2] * p[i][2]);
}
if (v[i][1] != 0 && v[i][2] != 0 && j >= v[i][0] + v[i][1] + v[i][2]) {
int cost = v[i][0] + v[i][1] + v[i][2];
dp[i][j] = max(dp[i][j], dp[i - 1][j - cost] + v[i][0] * p[i][0] + v[i][1] * p[i][1] + v[i][2] * p[i][2]);
}
}
}
cout << dp[m][n] << endl;
return 0;
}
解释
-
输入处理:读取总钱数
n和物品数m,使用二维数组v和p存储主件及其附件的价格和重要度。主件存储在v[i][0]和p[i][0],附件依次存储在v[i][1]和v[i][2]。 -
动态规划初始化:
dp[i][j]表示前i个主件在花费j元时的最大价值。初始状态为不购买任何物品。 -
状态转移:对于每个主件
i,遍历所有可能的花费j。依次处理四种购买情况:- 仅购买主件。
- 购买主件和附件1。
- 购买主件和附件2。
- 购买主件及两个附件。
每种情况检查是否可行(花费不超过j),并更新dp表。
-
结果输出:
dp[m][n]即为处理完所有主件且在n元限制下的最大价值。
总结
该方案通过分组背包的思路处理主件及其附件,确保附件的选择依赖于主件的购买,从而正确解决了依赖关系下的最大价值问题。
纪念品 CSP-2019
标签
动态规划DP 背包
分析
-
动态规划与贪心结合:对于每一天和第二天之间的价格差,计算每个纪念品的利润。若利润为正,则视为一个有效的投资机会。
-
完全背包问题:将每个有效投资机会视为物品,其成本是当天的价格,利润是次日的价格差。问题转化为每天处理一个完全背包问题,以最大化利润。
-
高效贪心优化:当金币数量很大时,使用贪心策略选择单位利润最高的物品,处理大部分金额,剩余部分使用动态规划处理。
代码
#include <bits/stdc++.h>
using namespace std;
int main() {
int T, N, M;
scanf("%d%d%d", &T, &N, &M);
vector<vector<int> > p(T, vector<int>(N));
for (int i = 0; i < T; ++i)
for (int j = 0; j < N; ++j)
scanf("%d", &p[i][j]);
for (int i = 0; i < T - 1; ++i) {
vector<pair<int, int> > v;
for (int j = 0; j < N; ++j) {
int c = p[i][j];
int pr = p[i+1][j] - c;
if (pr > 0)
v.push_back({c, pr});
}
if (v.empty()) continue;
int mx = 0;
for (size_t k = 0; k < v.size(); ++k) {
int c = v[k].first;
int pr = v[k].second;
int cnt = M / c;
int rem = M % c;
vector<int> dp(rem + 1, 0);
for (size_t j = 0; j < v.size(); ++j) {
int cc = v[j].first;
int prr = v[j].second;
for (int x = cc; x <= rem; ++x)
if (dp[x - cc] + prr > dp[x])
dp[x] = dp[x - cc] + prr;
}
mx = max(mx, cnt * pr + dp[rem]);
}
M += mx;
}
printf("%d\n", M);
return 0;
}
解释
-
输入处理:读取输入的天数T、纪念品种类N和初始金币M,以及每天每种纪念品的价格。
-
有效物品筛选:遍历相邻两天,筛选出利润为正的纪念品作为有效投资。
-
最大性价比计算:通过交叉相乘比较,确定每个纪念品的性价比,并收集所有具有最高性价比的候选物品。
-
动态规划处理余数:对每个候选物品计算商数和余数,使用动态规划处理余数部分的最大利润。
-
更新金币总数:选择每个候选物品中总利润最大的情况,更新金币总数。
总结
该算法结合贪心和动态规划,确保在金币数量较大时仍能高效计算,正确处理各种可能的组合情况。是一个多算法结合的题目,适合动态规划选手进行练习。
P1236 算24点
标签
搜索 递归 枚举
分析
很简单需要搜索每个状态的可能。
分别寻找四个可能,加减乘除。
然后递归搜索。
随便找到一个答案输出就可以了。
代码
#include <bits/stdc++.h>
using namespace std;
struct S {
int a;
char op;
int b;
int r;
};
bool dfs(vector<int> v, vector<S>& s) {
if (s.size() == 3) {
return (v.size() == 1 && v[0] == 24);
}
int n = v.size();
for (int i = 0; i < n; ++i) {
for (int j = i + 1; j < n; ++j) {
int a = v[i];
int b = v[j];
if (a < b) swap(a, b);
vector<int> nv;
for (int k = 0; k < n; ++k) {
if (k != i && k != j) {
nv.push_back(v[k]);
}
}
nv.push_back(a + b);
s.push_back({a, '+', b, a + b});
if (dfs(nv, s)) {
return true;
}
s.pop_back();
nv.pop_back();
int sub = a - b;
if (sub > 0) {
nv.push_back(sub);
s.push_back({a, '-', b, sub});
if (dfs(nv, s)) {
return true;
}
s.pop_back();
nv.pop_back();
}
nv.push_back(a * b);
s.push_back({a, '*', b, a * b});
if (dfs(nv, s)) {
return true;
}
s.pop_back();
nv.pop_back();
if (b != 0 && a % b == 0) {
int div = a / b;
nv.push_back(div);
s.push_back({a, '/', b, div});
if (dfs(nv, s)) {
return true;
}
s.pop_back();
nv.pop_back();
}
}
}
return false;
}
int main() {
vector<int> v(4);
for (int i = 0; i < 4; ++i) {
scanf("%d", &v[i]);
}
vector<S> s;
if (dfs(v, s)) {
for (int i = 0; i < s.size(); i++) {
printf("%d%c%d=%d\n", s[i].a, s[i].op, s[i].b, s[i].r);
}
} else {
printf("No answer!\n");
}
return 0;
}
解释
使用vector进行存储,可以更加高效。
总结
此题比较简单,很适合水。
周总结
本周内容主要是集中在动态规划系列题目上。
包括:背包 结合二分 等内容上。
总体还是难度偏大,当然,也有一些比较水的黄绿题。
下周的目标是复习之前做过的题,然后做一些新题。

浙公网安备 33010602011771号