直角三角形周長
題目鏈接:https://www.cupacm.com/newsubmitpage.php?id=1094
這是一道非常典型的枚舉題目,以下會一步一步分析如何對枚舉進行優化。
題目描述
一個直角三角形的周長是120的話,那麼它的三邊可以是20,48,52,或者24,45,51,還有30,40,50,有3種不同的解。現在你想知道如果給定一個直角三角形的周長,那麼這個周長最多能有多少解呢?假設邊長爲整數。
輸入
第一行一個,表示組測試數據。
每組測試數據佔一行僅含一個整數。
輸出
根據每組測試數據請求出以整數A爲周長的直角三角形的個數。(邊長都爲整數的直角三角形且周長爲整數A)
樣例輸入
2
12
120
樣例輸出
1
3
題解
這是一道非常典型的枚舉題目,以下會一步一步分析如何對枚舉進行優化,希望能對其他枚舉問題有所啓發。
0.三重循環
看到這道題,首先會想到枚舉三條邊,如果能符合三條邊加在一起等於周長,符合勾股定理,則滿足條件並計數。
//TLE
ans = 0;
cin >> l;
for (int i = 1; i < l; i++) {
for (int j = 1; j < l; j++) {
for (int k = 1; k < l; k++) {
if (i + j + k == l && i * i + j * j == k * k) {
ans++;
}
}
}
}
cout << ans / 2 << '\n';
毫無疑問,這樣最普通的枚舉時間超限了。
1.二重循環
現在,做一點優化,指定,這樣可以避免與重複枚舉(例如345,435是同一個答案)來節約時間,作爲斜邊,直接通過和周長便可以計算出,減少一重循環。
//TLE
ans = 0;
cin >> l;
for (int i = 1; i < l; i++) {
for (int j = i; j < l; j++) {
int k = l - i - j;
if (i * i + j * j == k * k) {
ans++;
}
}
}
cout << ans << '\n';
然而,還是TLE。
2.二重循環再優化
首先,我們先對問題進行數學分析:
已知 , ,
通過不等式可以得到 , 。
在二重循環的基礎上,對和的範圍進行限制
//716ms
ans = 0;
cin >> l;
for (int i = 1; i < l / 3; i++) {
for (int j = i; j < l / 2; j++) {
int k = l - i - j;
if (i * i + j * j == k * k) {
ans++;
}
}
}
cout << ans << '\n';
現在可以通過這道題目了,優化到了700多毫秒的時間。
再抓住兩邊之和大於第三邊的性質,但是仍然需要600毫秒的時間。
//598ms
ans = 0;
cin >> l;
for (int i = 1; i < l / 3; i++) {
for (int j = i; j < l / 2; j++) {
int k = l - i - j;
if (k < i + j && i * i + j * j == k * k) {
ans++;
}
}
}
cout << ans << '\n';
那麼,還能再優化嗎?
3.一重循環
讓我們重新回到數學上,2個方程2個未知數,我們可以輕鬆求出關於、的表達式。
已知 , ,
可以解得 。
通過數學方法,我們獲得了j的表達式,再判斷一下j小於l並且j是整數便可。
這樣的程序只有一重循環了,我們將程序從一開始的超時優化到了1ms,這是枚舉常見的優化方法——利用數學方法來減少循環次數。
//1ms
ans = 0;
cin >> l;
for (int i = 1; i < l / 3; i++) {
double j = l - (double) l * l / (2 * l - 2 * i);
if (i < j && j - (int) j < 1e-5) {
ans++;
}
}
cout << ans << '\n';
總結
枚舉通過窮舉,來遍歷一個問題的所有可能,找到符合條件的可能,便是答案。
在枚舉時,應當使用數學方法來對問題進行優化,可以有效減少枚舉的次數,提高算法的效率。