题解:学而思编程 三角形牧场
【题目来源】
学而思编程:三角形牧场
【题目描述】
和所有人一样,奶牛喜欢变化。它们正在设想新造型的牧场。奶牛建筑师 Hei 想建造围有漂亮白色栅栏的三角形牧场。她拥有 \(n\) 块木板,每块的长度 \(l_i\) 都是整数,她想用所有的木板围成一个三角形使得牧场面积最大。
请帮助 Hei 小姐构造这样的牧场,并计算出这个最大牧场的面积。
【输入】
第 \(1\) 行:一个整数 \(n\);
第 \(2\) 到第 \((n+1)\) 行,每行一个整数,第 \((i+1)\) 行的整数 \(l_i\) 表示第 \(i\) 块木板的长度。
【输出】
仅一个整数:最大牧场面积乘以 \(100\) 然后舍尾的结果。如果无法构建,输出 \(−1\)。
【输入样例】
5
1
1
3
3
4
【输出样例】
692
【核心思想】
-
问题分析:给定 \(n\) 块木板长度 \(l_i\),需将所有木板分成三组(三条边),构成三角形使面积最大。输出面积 \(\times 100\) 的整数部分,无法构成则输出 \(-1\)。这是一个二维 0-1 背包 + 海伦公式问题,关键在于将木板分配问题转化为"前两边长度和是否可达"的布尔 DP,再枚举验证三角形条件。
-
算法选择:
- 二维 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)}\)
- 二维 0-1 背包:
-
关键步骤:
- 初始化:读取 \(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]:放入第二边
- 遍历 \(k\) 从 \(0\) 到 \(s/2\):
- 遍历 \(j\) 从 \(0\) 到 \(s/2\):
- 枚举验证:
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)):用海伦公式计算面积并更新最大值
- 遍历 \(j\) 从 \(0\) 到 \(s/2\):
- 输出
ans
-
时间/空间复杂度:
- 时间复杂度:\(O(n \times s^2)\),\(s\) 为总长度,三维 DP 状态数
- 空间复杂度:\(O(n \times s^2)\),DP 数组
f[n][s/2][s/2]
-
二维 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
浙公网安备 33010602011771号