题解:学而思编程 三角形牧场

【题目来源】

学而思编程:三角形牧场

【题目描述】

和所有人一样,奶牛喜欢变化。它们正在设想新造型的牧场。奶牛建筑师 Hei 想建造围有漂亮白色栅栏的三角形牧场。她拥有 \(n\) 块木板,每块的长度 \(l_i\) 都是整数,她想用所有的木板围成一个三角形使得牧场面积最大。

请帮助 Hei 小姐构造这样的牧场,并计算出这个最大牧场的面积。

【输入】

\(1\) 行:一个整数 \(n\)

\(2\) 到第 \((n+1)\) 行,每行一个整数,第 \((i+1)\) 行的整数 \(l_i\) 表示第 \(i\) 块木板的长度。

【输出】

仅一个整数:最大牧场面积乘以 \(100\) 然后舍尾的结果。如果无法构建,输出 \(−1\)

【输入样例】

5
1
1
3
3
4

【输出样例】

692

【核心思想】

  1. 问题分析:给定 \(n\) 块木板长度 \(l_i\),需将所有木板分成三组(三条边),构成三角形使面积最大。输出面积 \(\times 100\) 的整数部分,无法构成则输出 \(-1\)。这是一个二维 0-1 背包 + 海伦公式问题,关键在于将木板分配问题转化为"前两边长度和是否可达"的布尔 DP,再枚举验证三角形条件。

  2. 算法选择

    • 二维 0-1 背包f[i][j][k] 表示前 \(i\) 根木板中,能否选出两组使第一组和为 \(j\)、第二组和为 \(k\)
    • 第三边推导:若前两边的和分别为 \(j\)\(k\),则第三边为 \(s - j - k\)\(s\) 为总长度)
    • 三角形判定:三边满足 \(a+b>c\), \(a+c>b\), \(b+c>a\)(等价于任意两边之和大于第三边)
    • 海伦公式求面积\(p = \frac{a+b+c}{2}\)\(S = \sqrt{p(p-a)(p-b)(p-c)}\)
  3. 关键步骤

    • 初始化:读取 \(n\) 和木板长度 \(l[1..n]\),计算总长度 \(s = \sum_{i=1}^{n} l_i\)
    • DP 初始化f[0][0][0] = 1(不选任何木板时,两边和均为 \(0\) 可达)
    • 二维背包转移(遍历 \(i\)\(1\)\(n\)):
      • 遍历 \(j\)\(0\)\(s/2\)
        • 遍历 \(k\)\(0\)\(s/2\)
          • f[i][j][k] = f[i-1][j][k]:第 \(i\) 根不放入任何边
          • \(j \geq l_i\)f[i][j][k] |= f[i-1][j-l_i][k]:放入第一边
          • \(k \geq l_i\)f[i][j][k] |= f[i-1][j][k-l_i]:放入第二边
    • 枚举验证
      • ans = -1
      • 遍历 \(i\)\(0\)\(s/2\)
        • 遍历 \(j\)\(0\)\(s/2\)
          • 第三边 \(c = s - i - j\)
          • f[n][i][j] == 1\(i+j>c\), \(i+c>j\), \(j+c>i\)
            • ans = max(ans, cal(i, j, c)):用海伦公式计算面积并更新最大值
    • 输出 ans
  4. 时间/空间复杂度

    • 时间复杂度:\(O(n \times s^2)\)\(s\) 为总长度,三维 DP 状态数
    • 空间复杂度:\(O(n \times s^2)\),DP 数组 f[n][s/2][s/2]
  5. 二维 0-1 背包的核心思想

    • 三边分配降维:将 \(n\) 根木板分配到三边,等价于先确定前两边的和,第三边由总长减去前两边的和自动确定。用二维布尔数组记录前两边的和是否可达,将指数级枚举降为多项式级
    • 状态压缩f[i][j][k] 为布尔值,只关心"是否可达"而非方案数,用位运算 |= 高效更新
    • 剪枝优化\(j\)\(k\) 只需枚举到 \(s/2\),因为三角形任一边不超过半周长
    • 海伦公式:已知三边求面积的经典公式,将几何约束转化为可计算的优化目标
    • 适用于"多组分配 + 约束验证"类问题,多维背包将组合爆炸控制在可接受范围

【算法标签】

01背包

【代码详解】

// DFS版本,拿部分分
#include<bits/stdc++.h>
using namespace std;
#define int long long
int n, l[15], maxn = -1, plan[15];  // n: 木棒数量,l[15]: 木棒长度,maxn: 最大面积*100,plan[15]: 分配方案
void dfs(int step)  // 深度优先搜索函数
{
    if (step > n)  // 如果已经处理完所有木棒
    {
        int x = 0, y = 0, z = 0;  // 三条边的长度
        for (int i = 1; i <= n; i++)  // 计算三条边的总长度
        {
            if (plan[i] == 1)  // 分配给第一条边
                x += l[i];
            if (plan[i] == 2)  // 分配给第二条边
                y += l[i];
            if (plan[i] == 3)  // 分配给第三条边
                z += l[i];
        }
        if (x + y > z && x + z > y && y + z > x)  // 判断是否能构成三角形
        {
            double p = 1.0 * (x + y + z) / 2;  // 计算半周长
            double s = sqrt(p * (p - x) * (p - y) * (p - z));  // 计算三角形面积
            int t = s * 100;  // 面积乘以100
            maxn = max(maxn, t);  // 更新最大面积
        }
        return;  // 返回上一层
    }

    plan[step] = 1;  // 当前木棒分配给第一条边
    dfs(step + 1);  // 递归处理下一根木棒
    plan[step] = 2;  // 当前木棒分配给第二条边
    dfs(step + 1);  // 递归处理下一根木棒
    plan[step] = 3;  // 当前木棒分配给第三条边
    dfs(step + 1);  // 递归处理下一根木棒
}
signed main()
{
    cin >> n;  // 输入木棒数量
    for (int i = 1; i <= n; i++)  // 输入每根木棒的长度
        cin >> l[i];
    dfs(1);  // 从第1根木棒开始搜索
    cout << maxn;  // 输出最大面积*100
    return 0;
}
// 动态规划版本,AC
#include<iostream>
#include<cmath>
using namespace std;

int n, s, l[45], f[45][805][805], ans = -1;  // s: 总长度, f: DP数组, ans: 最大面积

// 检查三边是否能构成三角形
bool check(int a, int b, int c)
{
    return f[n][a][b] && a + b > c && a + c > b && b + c > a;
}

// 计算三角形面积(海伦公式)
int cal(int a, int b, int c)
{
    double p = (a + b + c) / 2.0;
    double s = sqrt(p * (p - a) * (p - b) * (p - c));
    return s * 100;  // 乘以100保留两位小数
}

int main()
{
    cin >> n;
    for (int i = 1; i <= n; ++i)
    {
        cin >> l[i];
        s += l[i];  // 计算总长度
    }

    f[0][0][0] = 1;  // 初始化
    for (int i = 1; i <= n; i++)
    {
        for (int j = 0; j <= s / 2; j++)
        {
            for (int k = 0; k <= s / 2; k++)
            {
                f[i][j][k] = f[i - 1][j][k];  // 不使用第i根木棍
                if (j >= l[i])
                {
                    f[i][j][k] |= f[i - 1][j - l[i]][k];  // 放入第一边
                }
                if (k >= l[i])
                {
                    f[i][j][k] |= f[i - 1][j][k - l[i]];  // 放入第二边
                }
            }
        }
    }

    // 枚举前两边的长度
    for (int i = 0; i <= s / 2; i++)
    {
        for (int j = 0; j <= s / 2; j++)
        {
            if (check(i, j, s - i - j))  // 第三边 = 总长 - 前两边
            {
                ans = max(ans, cal(i, j, s - i - j));
            }
        }
    }
    cout << ans << endl;

    return 0;
}

【运行结果】

5
1
1
3
3
4
692
posted @ 2026-07-05 14:21  团爸讲算法  阅读(12)  评论(0)    收藏  举报