---
# System prepended metadata

title: Nhân tử Lagrange

---

### Mở bài
Do dạo này Hà Nội mùa nồm nên tính mình hơi mát mát, xong viết cái blog này :sob: 

### Phương pháp nhân tử Lagrange
Trong kì học đầu tiên ở HUST, mình được thầy Nguyễn Xuân Thảo chỉ cho cái này ở bài cuối cùng trong môn GT1. Cụ thể là để tìm một cực trị của hàm nhiều biến $f(x_1, x_2, ..., x_n)$ dưới điều kiện $g(x_1, x_2, ..., x_n) = C$.

Ta thêm một biến Lagrange $\lambda$ vào và tìm các điểm tới hạn của hàm $L(x_1, x_2, ..., x_n, \lambda) = f(x_1, x_2, ..., x_n) + \lambda \times (g(x_1, x_2, ..., x_n) - C)$.


### Vậy nó có ứng dụng gì trong Lập trình
- Mình chịu, chỉ là giải được một bài hay hay bằng phương pháp này nên xin lỗi thầy Thảo vì em lười học T_T

#### Problem
**[UVA-12407](https://onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&page=show_problem&problem=3838)**: Speed Zones

- Tóm tắt: Chia mặt phẳng toạ độ Oxy thành $N$ phần $N \le 100$, mỗi phần có chiều rộng $100$ đơn vị, nếu bạn đứng ở điểm có tung độ trong $[100 \times (i - 1), 100 \times i)$ $(1 \le i < N)$ bạn có thể di chuyển với vận tốc $S_i$ $(1 \le S_i \le 10000)$. Tìm thời gian ngắn nhất để di chuyển từ $(0, 0)$ đến $(D, 100 \times N)$.


Ta có thể thấy rằng:
- Thời gian đi ngắn nhất là thời gian mình đi thẳng từ $1$ điểm $(x_1, y_1)$ đến điểm $(x_2, y_2)$.
- Cơ mà đi thẳng từ $(0, 0)$ đến $(D, 100 \times N)$ thì sẽ sai. Nếu ta coi đường đi của vật là $1$ tia sáng thì theo Snell thời gian ngắn nhất luôn bằng với thời gian $1$ tia sáng đi từ $A$ đến $B$, góc khúc xạ của tia sáng giữa $2$ môi trường khác nhau tính bằng Định Luật Snell $(\frac{\sin{\theta_1}}{\sin{\theta_2}} = \frac{v_1}{v_2})$, và khả năng là $\theta_1 \ne \theta_2$ rồi nên có thể tia sẽ không thẳng ([bài này cũng có thể giải bằng Snell đó](https://oj.vnoi.info/problem/bedao_g13_e)).
- Cơ mà mình không bàn đến Snell ở đây vì thế nên mình sẽ dùng phương pháp nhân tử Lagrange. Ta cần chia đoạn $[0, D]$ thành $N$ phần $x_1, x_2, ..., x_N$ thoả mãn $0 \le x_1, x_2, ..., x_N$ và  $x_1 + x_2 + ... + x_N = D$. Khi đó, bài toán trở thành tìm cực trị của hàm $f_(x_1, x_2, ..., x_N) = \Sigma^{N}_{i = 1} \frac{\sqrt{10000 + x_i^2}}{s_i}$ dưới điều kiện $g_(x_1, x_2, ..., x_N) = \Sigma^{N}_{i = 1} x_i - D$.
- Tính các đạo hàm riêng của $L(x_1, x_2, ..., x_n, \lambda) = f(x_1, x_2, ..., x_n) - \lambda \times g(x_1, x_2, ..., x_n)$:
        -  $\frac{\partial L}{\partial x_i} = \frac{x_i}{s_i\sqrt{10000 + x_i^2}} - \lambda$.
        -  $\frac{\partial L}{\partial \lambda} = \Sigma^{N}_{i = 1} x_i - D$.

- Điểm tới hạn thoả mãn:

\begin{cases}
    & \frac{x_i}{s_i\sqrt{10000 + x_i^2}} - \lambda = 0 \\
    & \Sigma^{N}_{i = 1} x_i = D
\end{cases}

- Tính được: $x_i = \frac{|100\lambda s_i|}{\sqrt{1 - (\lambda s_i)^2}} = \frac{100s_i}{\sqrt{\frac{1}{\lambda^2} - s_i^2}}$.

- Đến đây, mình thấy được với $\lambda$ càng tăng thì $x_i$ cũng tăng theo, do đó $\Sigma^{N}_{i = 1} x_i$ cũng sẽ tăng cùng. Do đó, ta có thể sử dụng chặt nhị phân để tìm $\lambda$ thoả mãn điều kiện $\Sigma^{N}_{i = 1} x_i = D$.

### Code
```cpp=
#include<bits/stdc++.h>
#define fi first
#define sc second
#define task ""
using namespace std;
int t, n, d, s[102];

long double sum(long double T) {
    long double res = 0;
    for (int i = 1; i <= n; ++i)
        res += 100 * T * s[i]/sqrt(1 - T * T * s[i] * s[i]);

    return res;
}

int main() {
    if (fopen(task".inp", "r")) {
        freopen(task".inp", "r", stdin);
        freopen(task".out", "w", stdout);
    }

    ios::sync_with_stdio(0);
    cin.tie(0); cout.tie(0);


    cin >> t;

    for (int i = 1; i <= t; ++i) {
        cin >> n >> d;

        long double lo = 0, hi = 1e9;
        for (int i = 1; i <= n; ++i) {
            cin >> s[i];
            hi = min(hi, sqrt((long double) 1.0/(s[i] * s[i])));
        }

        for (int i = 1; i <= 100; ++i) {
            long double mid = (lo + hi)/2;

            if (sum(mid) <= d)
                lo = mid;
            else
                hi = mid;
        }

        long double L = (lo + hi)/2, ans = 0;


        for (int i = 1; i <= n; ++i)
            ans += sqrt(10000 + (10000 * L * L * s[i] * s[i])/(1 - L *L * s[i] * s[i]))/s[i];

        cout << fixed << setprecision(6) << "Case " << i << ": " << ans << '\n';
    }
}
```

### Kết bài
Chúc mọi người có người yêu :sunglasses: 