[CUPOJ] 直角三角形周長 枚舉優化 題解

直角三角形周長

題目鏈接:https://www.cupacm.com/newsubmitpage.php?id=1094
這是一道非常典型的枚舉題目,以下會一步一步分析如何對枚舉進行優化。

題目描述

一個直角三角形的周長是120的話,那麼它的三邊可以是20,48,52,或者24,45,51,還有30,40,50,有3種不同的解。現在你想知道如果給定一個直角三角形的周長,那麼這個周長最多能有多少解呢?假設邊長爲整數。

輸入

第一行一個TT,表示TT組測試數據。1T100001\leq T\leq 10000
每組測試數據佔一行僅含一個整數AA0A1000000\leq A\leq 100000

輸出

根據每組測試數據請求出以整數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.二重循環

現在,做一點優化,指定iji\leq j,這樣可以避免iijj重複枚舉(例如345,435是同一個答案)來節約時間,kk作爲斜邊,直接通過ijij和周長便可以計算出kk,減少一重循環。

//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.二重循環再優化

首先,我們先對問題進行數學分析:
已知 i+j+k=li+j+k=l0&lt;ij&lt;k0&lt;i\leq j&lt;k
通過不等式可以得到 i&lt;l/3i&lt;l/3j&lt;l/2j&lt;l/2
在二重循環的基礎上,對iijj的範圍進行限制

//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個未知數,我們可以輕鬆求出jj關於iill的表達式。
已知 i+k+j=li+k+j=li2+j2=k2i^{2}+j^{2}=k^{2}
可以解得 j=ll2/(2l2i)j=l-l^{2}/(2l-2i)
通過數學方法,我們獲得了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';

總結

枚舉通過窮舉,來遍歷一個問題的所有可能,找到符合條件的可能,便是答案。
在枚舉時,應當使用數學方法來對問題進行優化,可以有效減少枚舉的次數,提高算法的效率。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章