UVa OJ 194 - Triangle (三角形)

Time limit: 30.000 seconds
限時30.000秒html

 

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.

ios

194img1Figure: Trianglegit





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.


github

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).

less

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.00000000ide

 

 

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需單獨列出。this

每一類型的參數位置均可選出一種做爲這一類的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做爲二進制數轉爲十進制的值。

194img2

每一類狀況最多三種,除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爲要求的參數的序號,n一、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個值時只有一種狀況。當給出四、五、6個值時,能夠用與之等價的3個值的狀況計算,而後用求出的解驗證給出的多餘值便可。這三種狀況分別列表以下:

194img3

太複雜了,已致於細講的意義不大,翻譯也就免了吧。能看懂的就看,看不懂的也沒有必要去作了。直接甩代碼(在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;
}
相關文章
相關標籤/搜索