题意:给出n个圆,和每个圆的半径,求出如果把这些圆放到一个矩形内,且每个圆都接触矩形的底边,求出矩形的最短长度。
题解:根据题目意思,如果矩形的长度要最短,说明圆都很紧密的靠在一起,所以每个圆至少和一个圆相切,相切并在同一平面上的两个圆心的水平距离公式是sqrt((r1 + r2) ^2 - (r1 - r2) ^2)。开始先将n个圆的半径全排列以确定摆放顺序,然后计算每个序列的矩形长度。这里运用一个pos[N]数组记录每个圆的圆心到矩形左边的距离,然后根据圆的半径和摆放方式更新,最后比较每次得到的矩形长度,较小的保留。
#include <stdio.h>
#include <math.h>
#include <algorithm>
using namespace std;
const int N = 10;
const double INF = 9999999999999;
double r[N], minn;
double pos[N];//记录每个点到矩形左边的距离
int n;
double cal(int i, int j) {//计算圆心的水平距离
double a = r[i] + r[j];
double b = r[i] - r[j];
return sqrt(a * a - b * b);
}
void judge() {
pos[0] = r[0];
for (int i = 0; i < n - 1; i++) {
pos[i + 1] = pos[i] + cal(i + 1, i);//更新每个圆的当前距离
if (pos[i + 1] < r[i + 1])
pos[i + 1] = r[i + 1];//如果当前距离加上两圆心水平距离小于下一个圆的半径,就将下一个圆的到矩形左边的距离置为它的半径
for (int j = 0; j < i + 1; j++)
if (pos[i + 1] - pos[j] < cal(i + 1, j))
pos[i + 1] = pos[j] + cal(i + 1, j);//如果两个大圆之间有一个半径非常小的圆,这个圆在更新当前距离的时候和两个大圆都相切,但是明显这样的话两个大圆就重叠了,所以让后面大圆的当前距离更新为加上与左边大圆相切后的距离
}
double temp = pos[n - 1];
for (int i = 0; i < n; i++) //有可能存在一个很大的圆的当前距离加自己的半径大于最后一个圆的当前距离,就替换掉,否则加上最后一个圆的半径
if (pos[i] + r[i] > temp)
temp = pos[i] + r[i];
if (temp < minn)
minn = temp;
}
int main() {
int t;
scanf("%d", &t);
while (t--) {
minn = INF;
scanf("%d", &n);
for (int i = 0; i < n; i++)
scanf("%lf", &r[i]);
sort(r, r + n);
do {
judge();
} while(next_permutation(r, r + n));//下一个排列
printf("%.3lf\n", minn);
}
return 0;
}