UVa OJ 194 - Triangle (三角形)

时间:2022-04-07 13:26:58

Time limit: 30.000 seconds
限时30.000秒

Problem
问题

A triangle is a basic shape of planar geometry. It consists of three straight lines and three angles in between. Figure 1 shows how the sides and angles are usually labeled.

UVa OJ 194 - Triangle (三角形)Figure: Triangle

A look into a book about geometry shows that many formulas for triangles exist:

The values of a, b, c, tex2html_wrap_inline64 , tex2html_wrap_inline66 , and tex2html_wrap_inline68 form a set of six parameters that fully define a triangle. If a large enough subset of these parameters is given, the missing ones can be calculated by using the formulas above.

You are to write a program that calculates the missing parameters for a given subset of the six parameters of a triangle. For some sets of parameters, it is not possible to calculate the triangle because either too few is known about the triangle or the parameters would lead to an invalid triangle. The sides of a valid triangle are greater than 0 and the angles are greater than 0 and less than tex2html_wrap_inline70 . Your program should detect this case and output: "Invalid input." The same phrase should be output if more than the minimal set needed to compute the triangle is given but the parameters conflict with each other, e.g. all three angles are given but their sum is greater than tex2html_wrap_inline70 .

Other sets of parameters can lead to more than one but still a finite number of valid solutions for the triangle. In such a case, your program should output: "More than one solution."

In all other cases, your program should compute the missing parameters and output all six parameters.

Input
输入

The first line of the input file contains a number indicating the number of parameter sets to follow. Each following line consists of six numbers, separated by a single blank character. The numbers are the values for the parameters a, tex2html_wrap_inline64 , b, tex2html_wrap_inline66 , c, and tex2html_wrap_inline68 respectively. The parameters are labeled as shown in figure 1. A value of -1 indicates that the corresponding parameter is undefined and has to be calculated. All floating-point numbers include at least eight significant digits.

Output
输出

Your program should output a line for each set of parameters found in the input file. If a unique solution for a valid triangle can be found for the given parameters, your program should output the six parameters a, tex2html_wrap_inline64 , b, tex2html_wrap_inline66 , c, tex2html_wrap_inline68 , separated by a blank character. Otherwise the line should contain the phrase

"More than one solution." or

"Invalid input."

as explained above.

The numbers in the output file should include at least six significant digits. Your calculations should be precise enough to get the six most significant digits correct (i.e. a relative error of 0.000001 is allowed).

Sample input
示例输入

4
47.9337906847 0.6543010109 78.4455517579 1.4813893731 66.5243757656 1.0059022695
62.72048064 2.26853639 -1.00000000 0.56794657 -1.00000000 -1.00000000
15.69326944 0.24714213 -1.00000000 1.80433105 66.04067877 -1.00000000
72.83685175 1.04409241 -1.00000000 -1.00000000 -1.00000000 -1.00000000

Sample output
示例输出

47.933791 0.654301 78.445552 1.481389 66.524376 1.005902
62.720481 2.268536 44.026687 0.567947 24.587225 0.305110
Invalid input.
Invalid input.

Analysis
分析

超难题目,推导过程非常繁复,极易出错。由于特殊情况很多,对逻辑抽象能力和思维严谨性要求很高。不建议作为竞赛练习题。

所给三角形的参数数量小于3个时无法计算,大于3个时一定可以计算,等于3个时的情况分为以下6种:角角角(AAA)、角角边(ASA)、边角边(SAS)、边边边(SSS)、边边角(SSA)。但就其中任意一种而言,又包括一种或多种已知参数位置,比如SAS就有三种,即aγb、bαc和cβa,而SSS就只有一种,即abc。对于一个类型中的多种参数位置,大多数是可以通过旋转3条边和3个角的标号使之成为同一种参数位置。例如所给的参数是cβa,顺时针旋转就变成了aαb,旋转后参数之间会存在一个映射关系。然而,注意上述六种情况中AAS和SSA各有一些情况是无法通过旋转使之统一的,比如所给参数为AAS类型的aαβ就无法通过旋转变成aαγ,而这两种情况实际是镜像的关系。因此将AAS的镜像SAA与ASS的镜像SSA需单独列出。

每一类型的参数位置都可选出一种作为这一类的base,其它旋转的情况可以选转成base,计算后再转回原来的次序即可。这样分类后,每一类的计算方法就是唯一的。由于只有3个位知量,因此每一类的运算都只需要三个公式即可,这些公式包括:用PI减两角得第三角、正弦定理求角、正弦定理求边、余弦定理求角、余弦定理求边五种。为方便起见,我们按顺序将输入的参数a、α、b、β、c、γ编号为[0]、[1]、[2]、[3]、[4]、[5]。例如所给参数为AAS类型的aαβ,那么计算过程为:先用αβ求出γ,再用正弦定理求出b和c。算式为:[5]=π-[1]-[3],[2]=[0]*sin([3])/sin([1]),[4]=[0]*sin([5])/sin([1])。

对于每一种类型的参数,我们还需要检查输入的是否合法,比如SSS就要判断任意两边之和都不能大于或等于第三边。按上述建模方法,将各种情况的计算列成表格,见图所示。aαbβcγ列表示哪些参数给出,0表示未给出,1表示给出。其中粗体字的是base,后面的数字为base作为二进制数转为十进制的值。

UVa OJ 194 - Triangle (三角形)

每一类情况最多三种,除base外的下面两种分别为base沿逆时针方向转动1次和2次的参数位置,反过来讲就是base下面的第1种逆时针转1次可得到base,第2种逆时针1次可得到base。在第1种的各参数中,base的[0]就是它的[2],base的[1]就是它的[3],以此类推得到参数映射表:234501,第2种的参数映射表为450123。

用统一的形式抽象化各三角函数,可以认为这些函数都是输入了一些参数并返回一个值。输入的参数统一为:表达aαbβcγ的6个浮点数的数组,然后指定数组中第几个参数是要求出的,接下来指定数组中哪三个参数是要参与运算的。注意到用两角求第三角只需要两个值参与运算,那第三个参数可赋值为任意值。设p为参数数组,nr为要求的参数的序号,n1、n2和n3为参与运算的参数的序号,那么前述五个三角函数可列出如下:

PI减两角得第三角:PIANGLE,p[nr]= -p[n1]-p[n2]

正弦定理求边:SINSIDE,p[nr]=p[n1]*sin(p[n2])/sin(p[n3])

正弦定理求角:SINANGLE,p[nr]=asin(p[n1]*sin(p[n2])/p[n3])

余弦定理求边:COSSIDE,p[nr]=sqrt(p[n1]*p[n1]+p[n2]*p[n2]-2*p[n1]*p[n2]*cos(p[n3]))

余弦定理求角:COSANGLE,p[nr]=acos((p[n1]*p[n1]+p[n2]*p[n2]-p[n3]*p[n3])/(2*p[n1]*p[n2]))

有了这五个三角函数,除AAA外,其它各类型参数的计算可列出如下:

ASA:PIANGLE(5, 1, 3, 0), SINSIDE(0, 4, 1, 5), SINSIDE(2, 4, 3, 5)

AAS:PIANGLE(5, 1, 3, 0), SINSIDE(2, 0, 3, 1), SINSIDE(4, 0, 5, 1)

SAA:PIANGLE(3, 1, 5, 0), SINSIDE(2, 0, 3, 1), SINSIDE(4, 0, 5, 1)

SAS:COSSIDE(4, 0, 2, 5), COSANGLE(1, 2, 4, 0), COSANGLE(3, 0, 4, 2)

SSS:COSANGLE(1, 2, 4, 0), COSANGLE(3, 0, 4, 2), COSANGLE(5, 0, 2, 4)

SSA:SINANGLE(3, 2, 1, 0), PIANGLE(5, 1, 3, 0), SINSIDE(4, 0, 5, 1)

ASS:SINANGLE(5, 4, 1, 0), PIANGLE(3, 1, 5, 0), SINSIDE(2, 0, 3, 1)

现在就可以用上面的列出的公式和数据计算给出3个参数情况下另3个参数的值了。当给出4个值时,分为4种情况:三角一边、角角边边、边角边角和角边角边,注意边角边角和角边角边其实为同一种情况的镜像,这和3个参数里面的AAS与SAA类似。给出5个值时分为2种情况,即缺一角或缺一边。给出6个值时只有一种情况。当给出4、5、6个值时,可以用与之等价的3个值的情况计算,然后用求出的解验证给出的多余值即可。这三种情况分别列表如下:

UVa OJ 194 - Triangle (三角形)

太复杂了,已致于细讲的意义不大,翻译也就免了吧。能看懂的就看,看不懂的也没有必要去做了。直接甩代码(在github上能搜到一个代码,可以AC,但它有很多情况是会算错的!这道题的测试例程也太。。。。)

Solution
解答

#include <iomanip>
#include <iostream>
#include <vector>
#include <map>
#include <cmath> typedef unsigned char uchar;
enum TRIFUNC{PIANGLE, SINSIDE, SINANGLE, COSSIDE, COSANGLE};
enum TRITYPE{NA, AAA, ASA, AAS, SAA, SAS, SSS, SSA, ASS};
const double dPi = 3.14159265358979323846, dPi2 = dPi / 2.0;
//与base的映射序号表
const int orderAry[3][6] = {
{0, 1, 2, 3, 4, 5}, {4, 5, 0, 1, 2, 3}, {2, 3, 4, 5, 0, 1},
};
//一行对应于一种TRITYPE,从ASA到ASS。一行中每5个为一子组,每一子组为TriFunc的后5个调用参数。
const int idxAry[][15] = { //数据参见文档
{PIANGLE, 5, 1, 3, 0, SINSIDE, 0, 4, 1, 5, SINSIDE, 2, 4, 3, 5},
{PIANGLE, 5, 1, 3, 0, SINSIDE, 2, 0, 3, 1, SINSIDE, 4, 0, 5, 1},
{PIANGLE, 3, 1, 5, 0, SINSIDE, 2, 0, 3, 1, SINSIDE, 4, 0, 5, 1},
{COSSIDE, 4, 0, 2, 5, COSANGLE, 1, 2, 4, 0, COSANGLE, 3, 0, 4, 2},
{COSANGLE, 1, 2, 4, 0, COSANGLE, 3, 0, 4, 2, COSANGLE, 5, 0, 2, 4},
{SINANGLE, 3, 2, 1, 0, PIANGLE, 5, 1, 3, 0, SINSIDE, 4, 0, 5, 1},
{SINANGLE, 5, 4, 1, 0, PIANGLE, 3, 1, 5, 0, SINSIDE, 2, 0, 3, 1},
};
//包括五种三角函数,从给定参数表中的某几个值(指定序号)计算出另一个值(指定序号)
void TriFunc(double *p, TRIFUNC fn, size_t nr, size_t n1, size_t n2, size_t n3)
{
switch (fn)
{
// PI减两角得第三角
case PIANGLE: p[nr] = dPi - p[n1] - p[n2]; break;
// 正弦定理求边
case SINSIDE: p[nr] = p[n1] * sin(p[n2]) / sin(p[n3]); break;
// 正弦定理求角
case SINANGLE: p[nr] = asin(p[n1] * sin(p[n2]) / p[n3]); break;
// 余弦定理求边
case COSSIDE: p[nr] = sqrt(p[n1] * p[n1] + p[n2] * p[n2] -
2 * p[n1] * p[n2] * cos(p[n3])); break;
// 余弦定理求角
case COSANGLE: p[nr] = acos((p[n1] * p[n1] + p[n2] * p[n2] -
p[n3] * p[n3]) / (2 * p[n1] * p[n2])); break;
}
}
// 检测三边长度是否合法
int CheckSides(double *p)
{
return (p[0] >= p[2] + p[4] || p[2] >= p[0] + p[4] ||
p[4] >= p[2] + p[0]);
}
// 检测边边角类型是否可以计算出唯一解
int CheckSSA(double *p, size_t nIdx1, size_t nIdx2, size_t nIdx3)
{
if (p[nIdx2] >= dPi || (p[nIdx2] >= dPi2 && p[nIdx1] < p[nIdx3]))
return 1;
if (p[nIdx2] < dPi2) {
double dTmp = sin(p[nIdx2]) * p[nIdx3];
if (p[nIdx1] < dTmp - 1e-15) return 1;
if (p[nIdx1] > dTmp + 1e-15 && p[nIdx1] < p[nIdx3]) return 2;
}
return 0;
}
// 检测所给三角形的参数是否非法
int Checker(TRITYPE type, double *pParams)
{
switch (type)
{
case AAA: return 1; // AAA类型为非法
case ASA: return (pParams[1] + pParams[3] >= dPi); //检测给定两角和是否大于PI
case AAS: return (pParams[1] + pParams[3] >= dPi); //检测给定两角和是否大于PI
case SAA: return (pParams[1] + pParams[5] >= dPi); //检测给定两角和是否大于PI
case SAS: return (pParams[5] >= dPi); //检测给定角是否大于PI
case SSA: return CheckSSA(pParams, 0, 1, 2); //专用函数检测
case ASS: return CheckSSA(pParams, 0, 1, 4); //专用函数检测
case SSS: return CheckSides(pParams); //检测三边是否合法
}
} int main(void)
{
//求解器数据初始化,数据参见文档
std::map<uchar, TRITYPE> solvers;
solvers[21] = AAA; solvers[22] = ASA; solvers[52] = AAS; solvers[49] = SAA;
solvers[41] = SAS; solvers[42] = SSS; solvers[56] = SSA; solvers[50] = ASS;
solvers[23] = ASA; solvers[60] = AAS; solvers[54] = ASA; solvers[30] = ASA;
solvers[58] = SSS; solvers[31] = ASA; solvers[62] = SSS; solvers[63] = ASA; //设定输出精度,保留6位小数
std::cout.precision(6);
size_t nLines = 0;
for (std::cin >> nLines; nLines > 0; --nLines) {
double params[6], tmpParams[6];
uchar nParamCnt = 0, cFlags = 0;
for (size_t j = 0; j < 6; ++j) { //录入一行6个参数
std::cin >> params[j];
if (params[j] <= 0 && params[j] != -1.0) {
nParamCnt = 0; //若参数为负但不为-1则判定参数非法
break;
}
else if(params[j] > 0) { //用cFlags表示有效参数的位置
cFlags |= (1 << (5 - j));
++nParamCnt;
}
}
if (nParamCnt < 3) { //如果参数数量小于3则判定参数非法
std::cout << "Invalid input." << std::endl;
continue;
}
int *pOrder = (int*)&orderAry[0][0];
TRITYPE type = solvers[cFlags];
//查找该有效参数位置属于哪一个base类型,pOrder为其与所属base的映射
for (; type == NA; pOrder += 6) {
cFlags = ((cFlags & 0x03) << 4) | (cFlags >> 2);
type = solvers[cFlags];
}
//排序为base要求的顺序(旋转三角形)
for (int i = 0; i < 6; ++i) tmpParams[i] = params[pOrder[i]];
int nr = Checker(type, tmpParams); //检测输入数据是否合法
if (nr == 0) { // 0为合法,1为非法,2为多解
for (size_t i = 0; i < 3; ++i) { //求解未知的三个参数
const int *p = &idxAry[type - 2][i * 5];
TriFunc(tmpParams, TRIFUNC(p[0]), p[1], p[2], p[3], p[4]);
}
for (int i = 0; i < 6 && nr == 0; ++i) { //验证求解的解果与输入是否对应
double dTmp = tmpParams[i];
if ((cFlags & (1 << (5 - i))) == 0) params[pOrder[i]] = dTmp;
else if (std::abs(params[pOrder[i]] - dTmp) > 1e-7) nr = 1;
}
}
if (nr == 0) //按要求格式输出计算的结果
for (int i = 0; i < 6; ++i)
std::cout << std::fixed << params[i] << (i == 5 ? "\n" : " ");
else if (nr == 1) std::cout << "Invalid input." << std::endl;
else std::cout << "More than one solution." << std::endl;
}
return 0;
}