算法题练习(第四周)

金明的预算方案【NOIP2006提高组】

分析

该问题属于带有依赖关系的背包问题,主件和附件的购买必须满足先决条件。每个主件最多有两个附件,购买附件前必须先购买对应的主件。我们需要在总预算内最大化物品的价值总和(价格乘以重要度)。

步骤分析:

  1. 数据存储: 将每个主件及其附件的信息分别存储。主件的价格和重要度存储在数组v[i][0]p[i][0]中,附件存储在对应的v[i][1]p[i][1]v[i][2]p[i][2]中。

  2. 动态规划: 使用二维数组f[i][j]表示前i个物品在预算j时的最大价值。对于每个主件,考虑四种购买组合:仅主件、主件 + 附件1、主件 + 附件2、主件 + 两个附件。更新动态规划数组时,取这些组合中的最大值。

  3. 转移方程: 对于每个主件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;
}

解释

  1. 输入处理: 读取物品信息,将主件存入对应位置,附件添加到所属主件的附件列表中。

  2. 动态规划初始化: 初始时没有购买任何物品,价值为0

  3. 状态转移: 对于每个物品i(视为可能的主件),遍历预算j,尝试四种购买组合,更新最大价值。

  4. 结果输出: 最终结果为处理完所有物品后的最大价值f[m][n]

总结

该解法通过动态规划处理主件及其附件的组合购买情况,确保在预算内获得最大价值。每个主件的四种购买组合被逐一检查并更新最优解,最终结果正确且高效。

传纸条【NOIP2008提高组】

标签

动态规划DP 费用流

分析

  1. 状态表示:使用三维数组 F[k][i][j] 表示两个人同时走到第 k 步时,第一个人的纵坐标为 i,第二个人的纵坐标为 j 时的最大好感度之和。这里 k 表示两个人的横纵坐标之和,即 x1 + y1 = x2 + y2 = k

  2. 状态转移:每一步两个人可以分别从左边或上边移动过来,共有四种转移方式。我们需要考虑上一步所有可能的合法状态,取最大值加上当前两个点的好感度。

  3. 避免重复:通过限制 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;
}

解释

  1. 输入处理:读取矩阵的行数 m 和列数 n,以及矩阵中的每个元素。

  2. 初始化:将动态规划数组 F 初始化为 -1,表示不可达状态。初始状态 F[2][1][1] = 0 表示两个人在起点时的好感度和为0

  3. 动态规划转移:遍历每个可能的步数 k,以及每个可能的纵坐标 ij,确保 i < j。计算当前状态的最大值,并更新当前状态。

  4. 结果输出:输出 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;
}

解释

  1. 输入处理:读取总钱数 n 和物品数 m,使用二维数组 vp 存储主件及其附件的价格和重要度。主件存储在 v[i][0]p[i][0],附件依次存储在 v[i][1]v[i][2]

  2. 动态规划初始化dp[i][j] 表示前 i 个主件在花费 j 元时的最大价值。初始状态为不购买任何物品。

  3. 状态转移:对于每个主件 i,遍历所有可能的花费 j。依次处理四种购买情况:

    • 仅购买主件。
    • 购买主件和附件1。
    • 购买主件和附件2。
    • 购买主件及两个附件。
      每种情况检查是否可行(花费不超过 j),并更新 dp 表。
  4. 结果输出dp[m][n] 即为处理完所有主件且在 n 元限制下的最大价值。

总结

该方案通过分组背包的思路处理主件及其附件,确保附件的选择依赖于主件的购买,从而正确解决了依赖关系下的最大价值问题。

纪念品 CSP-2019

标签

动态规划DP 背包

分析

  1. 动态规划与贪心结合:对于每一天和第二天之间的价格差,计算每个纪念品的利润。若利润为正,则视为一个有效的投资机会。

  2. 完全背包问题:将每个有效投资机会视为物品,其成本是当天的价格,利润是次日的价格差。问题转化为每天处理一个完全背包问题,以最大化利润。

  3. 高效贪心优化:当金币数量很大时,使用贪心策略选择单位利润最高的物品,处理大部分金额,剩余部分使用动态规划处理。

代码

#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;
}

解释

  1. 输入处理:读取输入的天数T、纪念品种类N和初始金币M,以及每天每种纪念品的价格。

  2. 有效物品筛选:遍历相邻两天,筛选出利润为正的纪念品作为有效投资。

  3. 最大性价比计算:通过交叉相乘比较,确定每个纪念品的性价比,并收集所有具有最高性价比的候选物品。

  4. 动态规划处理余数:对每个候选物品计算商数和余数,使用动态规划处理余数部分的最大利润。

  5. 更新金币总数:选择每个候选物品中总利润最大的情况,更新金币总数。

总结

该算法结合贪心和动态规划,确保在金币数量较大时仍能高效计算,正确处理各种可能的组合情况。是一个多算法结合的题目,适合动态规划选手进行练习。

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进行存储,可以更加高效。

总结

此题比较简单,很适合水。

周总结

本周内容主要是集中在动态规划系列题目上。

包括:背包 结合二分 等内容上。

总体还是难度偏大,当然,也有一些比较水的黄绿题。

下周的目标是复习之前做过的题,然后做一些新题。

posted @ 2025-03-24 21:18  Easoncalm  阅读(14)  评论(0)    收藏  举报