洛谷 P1284 三角形牧场 题解

题目大意

洛谷 P1284 三角形牧场

给定序列 \(l_{[1..n]}\),将元素分成三组,求以各组元素总和为三边边长的三角形的面积的最大值。

思路分析

这是一道背包 DP 的题目。首先我们假设 \(dp_{i,j,k}\) 表示能否构造以 \(i,j,k\) 为三边边长的三角形。由于一个元素可以加到任意一条边的长度上,所以其状态转移方程应为:

\[dp_{i,j,k}=dp_{i-l_x,j,k} \lor dp_{i,j-l_x,k} \lor dp_{i,j,k-l_x} \]

其中 \(l_x\) 表示当前遍历到的元素,\(dp_{0,0,0}=1\)。求答案时枚举三条边长,用海伦公式计算即可。但注意到 \(i,j,k\le1600\),所以这样写必定在枚举边长时超时。考虑优化。

由于所有元素都会被分在某一组,所以周长 \(i+j+k\) 恒为序列中元素总和,只要知道两条边长,第三条边的长度便确定下来了。我们用 \(i\) 表示当前遍历的元素,\(j,k\) 分别表示其中两条边长,则边界条件仍为 \(dp_{0,0,0}=1\),状态转移方程变为:

\[dp_{i,j,k}=dp_{i-1,j-l_i,k}\lor dp_{i-1,j,k-l_i}\lor dp_{i-1,j,k} \]

注意到当前状态的转移只跟 \(dp_{i-1}\) 有关,可以利用滚动数组优化空间。注意 \(j,k\) 要倒着枚举。求答案时则枚举两条边长,求出第三条边的长度,注意三角形两边之和大于第三边

代码呈现

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

const int N=45,M=1605;
int n;
int l[N];
bool dp[M][M];

inline bool check(int a,int b,int c){ return a+b+c>max({a,b,c})*2; } // 判断能否成为三角形
inline double calc(int a,int b,int c){ // 海伦公式算面积
    double p=(a+b+c)/2.0; // 注意别写成 2
    return sqrt(p*(p-a)*(p-b)*(p-c));
}
int main(){
    scanf("%d",&n);
    for (int i=1;i<=n;++i) scanf("%d",l+i);
    int sum=0;
    for (int i=1;i<=n;++i) sum+=l[i];
    dp[0][0]=1;
    for (int i=1;i<=n;++i){
        for (int j=sum/2;j>=0;--j){ // 优化,a+b>sum-a-b -> a,b<sum/2
            for (int k=sum/2;k>=0;--k){
                if (j-l[i]>=0) dp[j][k]|=dp[j-l[i]][k];
                if (k-l[i]>=0) dp[j][k]|=dp[j][k-l[i]];
            }
        }
    }
    int ans=-1;
    for (int i=sum/2;i>0;--i){
        for (int j=sum/2;j>0;--j){
            if (dp[i][j] && check(i,j,sum-i-j))
                ans=max(ans,int(calc(i,j,sum-i-j)*100)); // 题目要求乘 100 后舍尾
        }
    }
    printf("%d",ans);
    return 0;
}
posted @ 2025-12-14 16:24  CodingJuRuo  阅读(0)  评论(0)    收藏  举报