【題解】A23330.最大四邊形面積

Macw發表於2024-06-11

題目連結:最大四邊形面積

題目有配圖參考,應該非常好理解。有一個動點 \(P\) 在笛卡爾座標系的第一象限內隨機在一條函式式為 \(y = kx + b\) 的直線上移動。問這個座標系原點與動點所能構成的矩形的最大面積是多少。

思路分析

本題可以用暴力三分的解法,也可以使用數學的方法來快速計算答案。關於三分演算法的程式碼和解釋在本文的後半部分。

前置知識:

  1. 瞭解並熟悉基本代數函式。
  2. 瞭解一元二次函式。
  3. 擁有初中數學基礎更好。

如果對一元二次函式不熟悉,可以參考文章:一元二次函式 by 一隻姜

計算矩形的面積比較簡單,那就是矩形的底邊長乘上矩形的高。很顯然,矩形的底邊長就是這個動點 \(P\)\(x\) 座標,高就是動點 \(P\)\(y\) 座標。因此矩形的面積應該為 \(S_{area} = x \cdot y = x \cdot (kx + b)\)。透過化簡可以的得到面積 \(S_{area} = kx^2 + bx\)。這是一個一元二次函式 。

很顯然,對於這個一元二次函式,這個函式的最大值就是本問題的解。如果這個一元二次函式的開口向下,那麼我們就需要求出這個函式的頂點座標,其中頂點的 \(y\) 座標就是答案。但如果這個一元二次函式的開口向上,那麼這個矩形的面積就是無限大。

如何判斷函式的開口朝向?如果 \(k > 0\) 說明這個二次函式開口向上,答案為無窮大。如果 \(k < 0\),說明這個二次函式的開口向下,透過頂點公式可以得到最大值 \(S_{area} = y = \frac{4kc - b^2}{4k}\),由於 \(c\)\(0\),進一步化簡可得 \(S_{area} = y = \frac{-b^2}{4k}\)

那麼當 \(k\) 等於 \(0\) 的時候呢?這個時候就不是一個一元二次函式了,而是一個線性函式。可以得出結論,這個矩形的面積也是無窮大。

總結

  • 如果 \(k \ge 0\),輸出 \(-1\)
  • 如果 \(k < 0\),輸出 \(\frac{- b^2}{4k}\)

三分演算法

三分演算法 (Ternary Search) 是一種用於在單峰函式(即函式在一個區間內單調遞增,然後單調遞減,或者先單調遞減然後單調遞增)的範圍內找到 最大值最小值 的最佳化方法。它的工作原理於二分搜尋類似,但每次將搜尋區間分成三部分,而不是兩部分。

在二分演算法中,我們一般是以區間的中值進行二分。但在三分演算法,我們需要將區間分成了三個部分。設當前列舉的區間在 \([L, R]\),那麼我們需要選擇兩個內部點將這個區間分隔開。通常情況下,我們會選擇區間的左三分之一和有三分之一作為分割點,座標分別是為 \(left\_third = L + \frac{R - L}{3}\)\(right\_third = R - \frac{R-L}{3}\)。之後的邏輯與二分類似,每次可以排除左三分之一或者右三分之一(取決於列舉時的實際情況)。最後該演算法就可以在對數時間內快速求解。

在這道題中,我們並不直接三分本問題的解,而是計算可以構成最大矩形面積的 \(P\) 點橫座標。由於 \(P\) 點只能在第一象限中移動,那麼 \(P\) 點的移動區間應該為 \((0, -\frac{b}{k})\)。因此我們用程式碼列舉這個區間即可。

結論證明

為了驗證公式結論的正確性,可以透過分析面積的變化率來證明。為方便起見,在後問中定義 \(f(x) = S = kx + b\)

首先先根據面積公式可以得到 \(S_{area} = kx^2 + bx\)。為了讓 \(S_{area}\) 的大小盡可能大,我們需要找到這個函式的 \(\mathtt{Local \space Maximum}\)。若一個點滿足 \(\mathtt{Local \space Maximum}\) 則需要有以下兩個前提:

  1. \(f'(x) = \frac{dS}{dx} = 0\)
  2. \(f''(x) = \frac{d^2S}{dx^2} < 0\)

計算可得 \(f'(x) = 2kx + b\)\(f''(x) = 2k\)

因此,只有當 \(k < 0\) 的時候才滿足條件。其餘情況一律輸出 \(-1\) 即可。那麼對於 \(k < 0\) 的時候,我們需要計算 \(x\) 滿足 \(2kx + b = 0\) 的解。透過移項可以得出 \(x = \frac{-b}{2k}\)。透過代入 \(x = \frac{-b}{2k}\) 到原式可得 \(S_{area} = \frac{4kc - b^2}{4k}\),與上述結論相同。

證畢 \(Q.E.D\)

AC 程式碼

使用數學方法的 AC 程式碼如下:

#include <iostream>
#include <algorithm>
#define int long long
using namespace std;

int t;
double a, b;

signed main(){
    ios::sync_with_stdio(0);
    cin.tie(0); cout.tie(0);
    cin >> t;
    while(t--){
        cin >> a >> b;
        if (a >= 0) {
            printf("-1\n");
            continue;
        }
        double x = (b * (-1)) / (2 * a);
        double ans = x * (a * x + b);
        printf("%.10lf\n", ans);
    }
    return 0;
}

使用三分法的 AC 程式碼如下:

#include <iostream>
#include <cmath>
#include <limits>
using namespace std;

int t; double k, b;

// 計算正方形面積
double area(double k, double b, double x) {
    return x * (k * x + b);
}

// 三分法
double ternarySearch(double k, double b) {
    double left = 0;
    
    // 與x軸的交點橫座標。
    double right = -b / k; 
    while (right - left > 1e-7) {
        double leftThird = left + (right - left) / 3;
        double rightThird = right - (right - left) / 3;
        
        if (area(k, b, leftThird) > area(k, b, rightThird)) 
            right = rightThird;
        else left = leftThird;
    }
    
    return area(k, b, (left + right) / 2);
}

int main() {
    cin >> t; 
    while (t--) {
        cin >> k >> b;
        if (k >= 0) cout << -1 << endl; 
        else {
            double maxArea = ternarySearch(k, b);
            printf("%.6lf\n", maxArea);
        }
    }
    return 0;
}

相關文章