GESP C++ 七级 DP 精选 4 题题解

本文档精选 8 道 GESP 七级。每道题包含:题目大意、状态定义、转移方程、边界条件、完整 AC 代码。


第 1 题:[GESP202506 七级] 调味平衡

题目大意

有 n 种调料,每种调料可以放在 A 组或 B 组(每组的重量累加)。求使两组总重量的差值 |x - y| 最小的方案,输出这个最小差值。

思路分析

01 背包变形 —— 差值 DP(可达性 DP)

  • 核心思想:把"差值"作为状态。
  • 状态定义:由于差值可能为负,加偏移量 OFFSET。
    • dp[j] = true 表示差值为 j - OFFSET 可达。
  • 转移:
    • 放到 A 组:差值 + w[i]
    • 放到 B 组:差值 - w[i]
  • 答案:从 OFFSET 往两边找最近的 true。

AC 代码

#include <bits/stdc++.h>
using namespace std;

const int OFFSET = 50000;
const int MAXS = OFFSET * 2 + 10;
int n, w[105];
bool dp[MAXS];

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    
    cin >> n;
    int sum = 0;
    for (int i = 1; i <= n; i++) {
        cin >> w[i];
        sum += w[i];
    }
    
    // 初始差值为 0
    dp[OFFSET] = true;
    
    for (int i = 1; i <= n; i++) {
        bool tmp[MAXS] = {false};
        for (int j = 0; j < MAXS; j++) {
            if (dp[j]) {
                // 放到 A 组:差值 + w[i]
                if (j + w[i] < MAXS) {
                    tmp[j + w[i]] = true;
                }
                // 放到 B 组:差值 - w[i]
                if (j - w[i] >= 0) {
                    tmp[j - w[i]] = true;
                }
            }
        }
        memcpy(dp, tmp, sizeof(dp));
    }
    
    // 从差值 0 往两边找最近可达状态
    for (int d = 0; d <= sum; d++) {
        if (dp[OFFSET + d] || dp[OFFSET - d]) {
            cout << d << '\n';
            break;
        }
    }
    
    return 0;
}

第 2 题:[GESP202509 七级] 金币收集

题目大意

在二维平面上有 n 个金币,每个金币有坐标 (x[i], y[i])。从 (0,0) 出发,每次只能向右或向上移动,路径上遇到的金币可以收集。问最多能收集多少金币?

思路分析

二维偏序 → LIS 转化 + 二分优化

  1. 排序:按 x 坐标升序排序,x 相同时按 y 坐标升序排序。
  2. 转化:排序后 x 已递增,问题转化为求 y 坐标序列的最长不下降子序列。
  3. 二分优化 LIS:维护 tails 数组,tails[i] 表示长度为 i 的子序列最小末尾元素,用 lower_bound 二分,复杂度 O(n log n)。

AC 代码

#include <bits/stdc++.h>
using namespace std;

const int MAXN = 200005;
int n;

struct Point {
    int x, y;
    bool operator<(const Point& other) const {
        if (x != other.x) {
            return x < other.x;
        }
        return y < other.y;
    }
} p[MAXN];

int tails[MAXN];

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    
    cin >> n;
    for (int i = 1; i <= n; i++) {
        cin >> p[i].x >> p[i].y;
    }
    sort(p + 1, p + 1 + n);
    
    // 提取 y 数组,求 LIS
    int len = 0;
    for (int i = 1; i <= n; i++) {
        int v = p[i].y;
        int pos = lower_bound(tails + 1, tails + 1 + len, v) - tails;
        tails[pos] = v;
        len = max(len, pos);
    }
    
    cout << len << '\n';
    return 0;
}

第 3 题:[GESP202412 七级] 燃烧

题目大意

给定一棵 n 个节点的树,每条边有长度。在某个节点点火,火会沿着边蔓延,燃烧一条边所需时间等于边长。求从哪个节点点火使整棵树全部被点燃的时间最短,输出这个最短时间。

思路分析

树形 DP + 换根法(两遍 DFS)

  • 第一遍 DFS(子树信息):

    • down[u]:从 u 向下点燃整棵子树的最长时间。
    • 同时记录每个节点子树中的最大值、次大值,以及最大值来自哪个子节点。
  • 第二遍 DFS(换根):

    • up[u]:从 u 通过父节点 p 向上点燃其他部分的最长时间。
    • 换根时:若 u 是 p 的最大子节点,则取次大值;否则取最大值。
    • up[v] = max(up[u], best_from_siblings) + w(u,v)
  • 答案:min over all u of max(down[u], up[u])

AC 代码

#include <bits/stdc++.h>
using namespace std;

const int MAXN = 200005;
int n;

struct Edge {
    int to;
    long long w;
};

vector<Edge> g[MAXN];
long long down[MAXN], up[MAXN];
int fmax_child[MAXN];
long long fmax[MAXN], smax[MAXN];

// 第一遍:计算 down、最大值、次大值
void dfs1(int u, int fa) {
    fmax[u] = 0;
    smax[u] = 0;
    fmax_child[u] = -1;
    
    for (auto& e : g[u]) {
        int v = e.to;
        if (v == fa) {
            continue;
        }
        dfs1(v, u);
        long long val = down[v] + e.w;
        
        if (val > fmax[u]) {
            smax[u] = fmax[u];
            fmax[u] = val;
            fmax_child[u] = v;
        } else if (val > smax[u]) {
            smax[u] = val;
        }
    }
    down[u] = fmax[u];
}

// 第二遍:换根计算 up
void dfs2(int u, int fa) {
    for (auto& e : g[u]) {
        int v = e.to;
        if (v == fa) {
            continue;
        }
        // v 通过 u 能到达的最长路径
        long long best = up[u];
        if (v == fmax_child[u]) {
            best = max(best, smax[u]);
        } else {
            best = max(best, fmax[u]);
        }
        up[v] = best + e.w;
        dfs2(v, u);
    }
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    
    cin >> n;
    for (int i = 1; i < n; i++) {
        int a, b;
        long long c;
        cin >> a >> b >> c;
        g[a].push_back({b, c});
        g[b].push_back({a, c});
    }
    
    dfs1(1, -1);
    up[1] = 0;
    dfs2(1, -1);
    
    long long ans = 1e18;
    for (int i = 1; i <= n; i++) {
        ans = min(ans, max(down[i], up[i]));
    }
    
    cout << ans << '\n';
    return 0;
}

第 4 题:[GESP202606 七级] 消消乐

题目大意

给定一个字符串 s,每次操作可以选择一段相邻的、字符完全相同的连续子串将其消除,消除后两侧拼接。消除长度为 k 的子串得分为 k²。求消除整个字符串的最大得分。

思路分析

区间 DP(消除模型)

  • 状态定义:dp[l][r] 表示消除 s[l..r] 能获得的最大得分。
  • 转移方程:
    1. 直接切分:枚举分割点 k,dp[l][r] = max(dp[l][r], dp[l][k] + dp[k+1][r])
    2. 首尾合并:若 s[l] == s[m],可以在消除中间后合并,dp[l][r] = max(dp[l][r], dp[l+1][m-1] + dp[m][r])
  • 边界:dp[i][i] = 1(单个字符得 1 分)。
  • 计算顺序:先枚举区间长度 len = 1 → n,再枚举左端点 l。

AC 代码

#include <bits/stdc++.h>
using namespace std;

const int MAXN = 205;
int n;
string s;
int dp[MAXN][MAXN];

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    
    cin >> s;
    n = s.size();
    s = " " + s;
    
    // 初始化:单个字符得 1 分
    for (int i = 1; i <= n; i++) {
        dp[i][i] = 1;
    }
    
    // 枚举区间长度
    for (int len = 2; len <= n; len++) {
        for (int l = 1; l + len - 1 <= n; l++) {
            int r = l + len - 1;
            
            // 最坏情况:一个个消除
            dp[l][r] = len;
            
            // 情况 1:切分两半
            for (int k = l; k < r; k++) {
                dp[l][r] = min(dp[l][r], dp[l][k] + dp[k + 1][r]);
            }
            
            // 情况 2:首尾字符相同,可以合并消除
            if (s[l] == s[r]) {
                dp[l][r] = min(dp[l][r], dp[l + 1][r - 1] + 1);
            }
            
            // 情况 3:中间有字符与左端相同,可合并
            for (int k = l + 1; k <= r; k++) {
                if (s[k] == s[l]) {
                    dp[l][r] = min(dp[l][r],
                        dp[l + 1][k - 1] + dp[k][r]);
                }
            }
        }
    }
    
    cout << dp[1][n] << '\n';
    return 0;
}

三、八大 DP 模型速查表

模型 核心状态 典型转移 代表题
差值背包 dp[diff] 可达性转移 调味平衡
LIS 优化 tails[] 二分查找 O(n log n) 金币收集
换根树 DP down/up 两遍 DFS 燃烧
区间 DP dp[l][r] 枚举分割点 + 合并 消消乐
posted @ 2026-09-04 16:51  费先生  阅读(40)  评论(0)    收藏  举报