洛谷 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;
}

浙公网安备 33010602011771号