某考试 T2 yja
2.1 Description
在平面上找 n 个点, 要求这 n 个点离原点的距离分别为 r1, r2, ..., rn.
最大化这 n 个点构成的凸包面积, 凸包上的点的顺序任意.
2.2 Input Format
第一行一个整数 n.
接下来一行 n 个整数依次表示 ri .
2.3 Output Format
输出一个实数表示答案, 要求绝对误差或相对误差 ≤ 10−6.
2.4 Sample
2.4.1 Input
4
5
8
58
85
2.4.2 Output
2970
2.5 Constraints
对于前 20% 的数据, n ≤ 3;
对于前 40% 的数据, n ≤ 4;
对于另 20% 的数据, r1 = r2 = ... = rn;
对于 100% 的数据, 1 ≤ n ≤ 8, 1 ≤ ri ≤ 1000.
考试的时候看了一会题就感觉是个拉格朗日乘数法的题,但是我为什么往叉积求面积上想了23333,于是就出来了n个限制然后还得三分套三分套三分.......
于是在想如何优化求偏导都是零的过程中时间过去了2.5h 。。。(我好菜啊我现在退役算了)。
正解其实就是拉格朗日乘数法,但是求面积是用(1/2) * a * b * sin(a和b的夹角) ,于是这样就只有一个总限制,那就是所有角的和=2*π。
并且利用所有变量的偏导等于0,我们可以得到 λ = r1 * r2 *cos(r1和r2夹角) = .... ,因为最优情况肯定没有超过π的角,而cos在[0,π]上单调,所以我们二分一下λ 就可以得到最后的答案。
当然因为不一定所有点都在凸包上出现,并且每个点出现的顺序不一定,所以我们还要枚举选ri前几大的点(贪心)和它们的顺序(全排列一下就好了)。
#include<bits/stdc++.h> #define ll long long #define D double using namespace std; const int maxn=10; const D pi=acos(-1); D miu,l,r,thita[maxn]; int n,R[maxn]; inline D calc(){ D tot=0; for(int i=1;i<n;i++) thita[i]=acos(miu/(R[i]*R[i+1])),tot+=thita[i]; thita[n]=acos(miu/(R[n]*R[1])),tot+=thita[n]; return tot; } inline void solve(){ D ans=0,now,le,ri; sort(R+1,R+n+1); for(;n>=3;){ le=-R[1]*R[2],ri=-le; while(1){ l=le,r=ri; while(r-l>=1e-8){ miu=(l+r)/2; if(calc()>=2*pi) l=miu; else r=miu; } if(fabs(calc()-2*pi)<=1e-6){ now=0; for(int i=1;i<n;i++) now+=sin(thita[i])*(D)R[i]*(D)R[i+1]; now+=sin(thita[n])*(D)R[n]*(D)R[1]; ans=max(ans,now); } if(!next_permutation(R+1,R+n+1)) break; } sort(R+1,R+n+1),n--; for(int i=1;i<=n;i++) R[i]=R[i+1]; } printf("%.11lf\n",ans/2); } int main(){ scanf("%d",&n); for(int i=1;i<=n;i++) scanf("%d",R+i); solve(); return 0; }