1 条题解

  • 0
    @ 2025-10-23 17:09:18

    九头龙问题题解

    问题分析

    本题是一个树形动态规划问题,需要将NN个果子分配给MM个头,其中大头必须吃掉恰好KK个果子且必须包含最大的果子(编号为11的果子)。我们需要最小化九头龙吃树枝的难受值总和。

    关键观察

    1. 树结构:果子通过树枝连接形成一棵树,根节点是编号为11的最大果子。

    2. 难受值计算规则

      • 如果两个相邻果子被同一个头吃掉,那么这段树枝的难受值会被计入
      • 如果两个相邻果子被不同头吃掉,那么这段树枝不会被吃掉(难受值为00
    3. 约束条件

      • 大头必须吃掉恰好KK个果子
      • 每个头至少吃一个果子
      • 总共MM个头需要MM组果子

    算法思路

    状态设计

    使用树形DP,定义状态:

    • f[x][j][0]:在以xx为根的子树中,大头吃了jj个果子,且节点xx不是被大头吃掉时的最小难受值
    • f[x][j][1]:在以xx为根的子树中,大头吃了jj个果子,且节点xx是被大头吃掉时的最小难受值

    状态转移

    对于节点xx和它的子节点yy,考虑四种情况:

    1. xx不是大头吃,yy不是大头吃

      • 如果M=2M = 2,那么xxyy必须被不同的头吃掉,难受值为w[x][i]w[x][i]
      • 如果M>2M > 2,那么xxyy可以被同一个非大头吃掉,难受值为00
    2. xx不是大头吃,yy是大头吃

      • xxyy被不同头吃掉,难受值为00
    3. xx是大头吃,yy不是大头吃

      • xxyy被不同头吃掉,难受值为00
    4. xx是大头吃,yy是大头吃

      • xxyy被同一个头(大头)吃掉,难受值为w[x][i]w[x][i]

    转移方程

    对于每个子节点yy

    // x不是大头,y不是大头
    if (M == 2) {
        f[x][j][0] = min(f[x][j][0], temp[j-t][0] + min(f[y][t][0] + w[x][i], f[y][t][1]));
    } else {
        f[x][j][0] = min(f[x][j][0], temp[j-t][0] + min(f[y][t][0], f[y][t][1]));
    }
    
    // x是大头,y任意
    f[x][j][1] = min(f[x][j][1], temp[j-t][1] + min(f[y][t][0], f[y][t][1] + w[x][i]));
    

    可行性检查

    在开始DP之前,先检查是否满足基本条件:

    if (n - k < m - 1) {
        cout << -1 << endl;
        return 0;
    }
    

    这是因为:大头吃了KK个果子,剩下的NKN-K个果子要分配给M1M-1个头,每个头至少一个果子,所以必须满足NKM1N-K ≥ M-1

    代码实现详解

    #include <bits/stdc++.h>
    using namespace std;
    
    vector<int> g[305];  // 邻接表存储树的边
    vector<int> w[305];  // 存储边的权重(难受值)
    int f[305][305][2];  // DP数组
    int n, m, k;
    int temp[305][2];    // 临时数组,用于辅助DP
    
    void dp(int x, int fa) {
        // 初始化:x节点单独考虑时
        f[x][0][0] = 0;  // x不是大头吃,大头吃0个果子
        f[x][1][1] = 0;  // x是大头吃,大头吃1个果子
        
        // 遍历所有子节点
        for (int i = 0; i < g[x].size(); i++) {
            int y = g[x][i];
            if (y == fa) continue;
            
            dp(y, x);  // 递归处理子节点
            
            // 保存当前状态
            for (int j = 0; j <= k; j++) {
                temp[j][0] = f[x][j][0];
                temp[j][1] = f[x][j][1];
                f[x][j][0] = f[x][j][1] = 0x3f3f3f3f;  // 重置为无穷大
            }
            
            // 状态转移
            for (int j = k; j >= 0; j--) {          // 当前x子树中大头吃的果子数
                for (int t = 0; t <= j; t++) {      // 子节点y子树中大头吃的果子数
                    // 情况1:x不是大头吃
                    if (m == 2) {
                        // 只有两个头,x和y如果都不是大头吃,必须被不同的头吃
                        f[x][j][0] = min(f[x][j][0], 
                            temp[j-t][0] + min(f[y][t][0] + w[x][i], f[y][t][1]));
                    } else {
                        // 超过两个头,x和y如果都不是大头吃,可以被同一个非大头吃
                        f[x][j][0] = min(f[x][j][0], 
                            temp[j-t][0] + min(f[y][t][0], f[y][t][1]));
                    }
                    
                    // 情况2:x是大头吃
                    f[x][j][1] = min(f[x][j][1], 
                        temp[j-t][1] + min(f[y][t][0], f[y][t][1] + w[x][i]));
                }
            }
        }
    }
    
    int main() {
        cin >> n >> m >> k;
        
        // 可行性检查
        if (n - k < m - 1) {
            cout << -1 << endl;
            return 0;
        }
        
        memset(f, 0x3f, sizeof(f));
        
        // 读入树结构
        for (int i = 1; i < n; i++) {
            int u, v, z;
            cin >> u >> v >> z;
            g[u].push_back(v);
            g[v].push_back(u);
            w[u].push_back(z);
            w[v].push_back(z);
        }
        
        dp(1, 0);  // 从根节点1开始DP
        
        if (f[1][k][1] >= 0x3f3f3f3f) {
            cout << -1 << endl;
        } else {
            cout << f[1][k][1] << endl;
        }
        
        return 0;
    }
    

    复杂度分析

    • 时间复杂度O(N×K2)O(N \times K^2),其中N300N \leq 300K300K \leq 300,在可接受范围内
    • 空间复杂度O(N×K)O(N \times K)

    样例分析

    对于样例输入:

    8 2 4
    1 2 20
    1 3 4  
    1 4 13
    2 5 10
    2 6 12
    3 7 15
    3 8 5
    

    最优解为44,对应策略是:

    • 大头吃果子:11, 33, 77, 88
    • 小头吃果子:22, 44, 55, 66

    这样只有连接1133的树枝(难受值44)被大头吃掉,其他树枝要么不被吃,要么因为连接不同头的果子而被弄断(难受值00)。

    • 1

    信息

    ID
    3884
    时间
    1000ms
    内存
    256MiB
    难度
    10
    标签
    递交数
    1
    已通过
    1
    上传者