屋頂面積 題解


簡潔題意

輸入五個分數 \(a, b, c, d, e\)(格式 p/q,分子分母都是 \(1 \sim 15\) 的正整數):等腰三角形底邊 \(a\)、高 \(b\);長方形(煙囪)寬 \(d\)、高 \(e\),底部貼在三角形底邊上,右邊界離三角形右端點 \(c\)。保證 \(b < e\)、\(2(c + d) < a\)。輸出屋頂圖形的面積,以最簡分數 p/q 表示。

解法

屋頂 \(=\) 三角形 \(\cup\) 長方形,兩者有一塊重疊,所以

\[\text{面積} = \text{三角形} + \text{長方形} - \text{重疊} .\]

三角形是 \(\frac{a b}{2}\)、長方形是 \(d e\),關鍵在重疊的那一塊:長方形被三角形的斜邊切出一個梯形——兩條平行邊是斜邊在煙囪右邊界、左邊界處離地的高度 \(h_1\)、\(h_2\),梯形的高就是煙囪的寬 \(d\)。

\(h_1\)、\(h_2\) 靠相似三角形:右半個三角形底 \(m = \frac{a}{2}\)、高 \(b\),離右端點 \(x\) 的地方,斜邊離地高度是 \(x \cdot \frac{b}{m}\)。煙囪右邊界離右端點 \(c\)、左邊界離右端點 \(c + d\)(因為 \(2(c + d) < a\),整根煙囪都在右半邊),所以

\[h_1 = \frac{c \, b}{m}, \qquad h_2 = \frac{(c + d) \, b}{m}, \qquad \text{面積} = m b + d e - \frac{(h_1 + h_2) \, d}{2} .\]

拿範例 1 驗算:\(a = 6, b = 3, c = d = 1, e = 4\),\(m = 3\),\(h_1 = 1\),\(h_2 = 2\),面積 \(= 9 + 4 - \frac{3}{2} = \frac{23}{2}\)。

全程分數運算,正是第十四單元 Fraction 的舞台——公式怎麼寫,程式就怎麼寫:

Fraction m = a / 2;
Fraction h1 = c * b / m;
Fraction h2 = (c + d) * b / m;
Fraction ans = m * b + d * e - (h1 + h2) * d / 2;

三個語法重點:

  • a / 2 能編譯:14.7 講過,建構子 Fraction(long long a = 0, long long b = 1) 讓整數 2 自動變成 Fraction(2),再套用你的 operator /。注意「左邊必須是自己」:a / 2 可以、2 * a 不行。
  • 每個結果都經過建構子約分(14.5):ans 算完就是最簡分數,print() 直接印。
  • 讀入是 15/2 這種斜線格式:14.4 的 read() 用一個 char 把中間的斜線接住就好——cin >> p >> slash >> q;(14.8 的 >> 多載版也是同一招)。
原寫法 vs 結構體寫法

沒有結構體時,每個量都得拆成分子、分母兩個變數,四則運算各寫一個函式,主程式長這樣:

Pair m = divide(a, two);                              // a / 2
Pair h1 = divide(mul(c, b), m);                       // c * b / m
Pair h2 = divide(mul(add(c, d), b), m);               // (c + d) * b / m
Pair overlap = divide(mul(add(h1, h2), d), two);      // (h1 + h2) * d / 2
Pair ans = sub(add(mul(m, b), mul(d, e)), overlap);   // m * b + d * e - overlap

能動,但公式被 add(mul(...)) 包到看不出原形。結構體版把 + - * / 多載好,同一個公式變成:

Fraction m = a / 2;
Fraction h1 = c * b / m;
Fraction h2 = (c + d) * b / m;
Fraction ans = m * b + d * e - (h1 + h2) * d / 2;

優先序、括號、串接全都跟數學一樣(14.7:多載只換運算子的內容,不動它們的優先序)。

常犯錯誤

  • 忘了減掉重疊:直接 a * b / 2 + d * e,範例 1 會印 13/1。煙囪立在底邊上,下半截本來就在三角形裡面,那塊梯形算了兩次。
  • 把重疊當成三角形扣:重疊是斜邊切出的梯形,不是三角形。自我檢查:把煙囪往左右滑(改變 \(c\)),重疊面積應該跟著變——公式裡沒有 \(c\),就一定漏了東西。
  • 半個底邊用整數除法:寫成 Fraction m(a.p / 2, a.q),\(a = \frac{15}{2}\) 時 \(m\) 變成 \(\frac{7}{2}\) 而不是 \(\frac{15}{4}\)。交給 a / 2——分數除以整數,讓運算子去算。
  • 沒約分:答案要最簡分數;每一步都 return Fraction(...) 就不會漏。
  • 整數答案只印整數:面積是 \(6\) 要印 6/1,格式一律 p/q。
  • 讀入只讀數字、沒把斜線讀掉:cin >> p >> q 遇到 15/2 時,p 讀到 15,q 卡在 / 讀失敗,後面全部亂掉。中間補一個 char 變數接住斜線。

參考程式碼

#include <iostream>
#include <numeric>    // gcd
using namespace std;

struct Fraction {
    long long p;    // 分子
    long long q;    // 分母(保持為正)

    Fraction(long long a = 0, long long b = 1) {   // 預設 0/1;建構時順手約分
        p = a;
        q = b;
        reduce();
    }
    void reduce() {                      // 約分:化成最簡、分母為正
        long long g = gcd(p, q);
        p /= g;
        q /= g;
        if (q < 0) {
            p = -p;
            q = -q;
        }
    }
    void read() {                        // 這題的輸入長「15/2」:把中間的斜線當一個字元讀掉
        char slash;
        cin >> p >> slash >> q;
        reduce();
    }
    void print() {                       // 印成「分子/分母」
        cout << p << '/' << q << '\n';
    }
    bool operator < (const Fraction &b) const {     // 分母都為正:交叉相乘
        return p * b.q < b.p * q;
    }
    bool operator == (const Fraction &b) const {    // 都是最簡分數:比對成員即可
        return p == b.p && q == b.q;
    }
    Fraction operator + (const Fraction &b) const {
        return Fraction(p * b.q + b.p * q, q * b.q);
    }
    Fraction operator - (const Fraction &b) const {
        return Fraction(p * b.q - b.p * q, q * b.q);
    }
    Fraction operator * (const Fraction &b) const {
        return Fraction(p * b.p, q * b.q);
    }
    Fraction operator / (const Fraction &b) const {
        return Fraction(p * b.q, q * b.p);
    }
};

int main() {
    Fraction a, b, c, d, e;
    a.read();
    b.read();
    c.read();
    d.read();
    e.read();

    Fraction m = a / 2;                  // 半個底邊(整數 2 自動變成 Fraction(2))
    Fraction h1 = c * b / m;             // 煙囪右邊界處,斜邊離地的高度(相似三角形:c : m = h1 : b)
    Fraction h2 = (c + d) * b / m;       // 煙囪左邊界處,斜邊離地的高度
    // 三角形面積 + 長方形面積 − 重疊的梯形面積(上下底 h1、h2,高 d)
    Fraction ans = m * b + d * e - (h1 + h2) * d / 2;
    ans.print();
    return 0;
}

Fraction 來自語法書 14.7 貫穿範例,只把 read() 改成讀斜線格式。輸入的數都不超過 \(15\),乘來乘去也遠在 long long 之內。