1. 程式人生 > 實用技巧 >[OI筆記]利用拉格朗日乘數法求函式的最值

[OI筆記]利用拉格朗日乘數法求函式的最值

\(about\)

為什麼寫這篇\(Blog\)\(...\)

拉格朗日乘數法在今天訓練的一道題上用到了\(,\)當場\(wyj/pcf/csl\)都正確的推出了式子\(.\)

但我卻只會暴力\(DP.\)雖然也過了題但是多用了\(2k-3k\)的程式碼量\(.\)

但是賽後一看他們的\(1k\)左右的程式碼\(,\)人都傻了\(.\)

去網上搜了一下這種做法\(,\)自己推這題的時候偏導還求錯了\(/kk,\)最後在\(pcf\)的提示下才發現\(/kk.\)

所以今天來補一補數學知識\(T\) _ \(T\)

正文

拉格朗日乘數法\(,\)

求一個多元函式\(z = F(x,y)\)在滿足\(φ(x,y) = 0\)

的條件極值\(.\)

\((\) 由於筆者比較菜\(,\)而且不等式約束在\(OI\)中基本不會用拉格朗日乘數法來解決 \((\) 因為可以線性規劃\(,\)但那就和本文的主題無關了 \()\) \(,\)所以本文中不講不等式約束的解法\(.\) \()\)

可以轉化為\(,\)函式\(F(x,y,λ) = F(x,y) + λφ(x,y)\)的無條件極值\(.\)

無條件極值的求法\(?\)

\(F(x,y,λ)\)每個變數\((\)\(x,y,\)\(λ)\)都求一次偏導\(,\)然後令求出來的式子\(=0\)即可\(.\)

至於正確性\(……\)

正確性 $ : $ 對\(λ\)

求偏導就可以得到約束條件\(,\)然後\(λ\)的值不會影響函式值

具體的證明我不會\(,\)大概是隱函式什麼的\(?\)

這裡有兩個連結以供參考\(:\) Link1 Link2

實際上這種方法只能求出極值\(,\)並不能知道求出來的是極小值還是極大值\(,\)只有算出來之後才會知道

理論的內容就這麼多吧\(.\)

因為我不太會打\(\LaTeX\)的式子\(,\)所以就不放一些奇怪的很難打的式子\(,\)而用文字表述了\(.\)

批註:OI裡的函式基本上都很平滑,所以亂搞也沒關係的(

\(An\) \(Easy\) \(Example\)

求反比例函式\(y = \frac{1}{x}\)

上距離原點最近的點\(.\)

即最小化\(F(x,y) = \sqrt{x^2 + y^2},\)滿足約束\(φ(x,y) = xy-1 = 0\)

為了簡化問題\(,\)我們把\(F(x,y)\) 變成 \(F^2(x,y),\)

這樣問題要最小化的東西還是沒變\(,\)而且\(F(x,y) = x^2+y^2\)方便求偏導\(.\)

\(F(x,y,λ) = F(x,y) + λφ(x,y)\) $ = x^2+y^2+λ(xy-1)$

\(x\)求偏導得到\(:\) \(2x + λy = 0\)

\(y\)求偏導得到\(:\) \(2y + λx = 0\)

\(λ\)求偏導得到\(:\) \(xy - 1 = 0\)

最後解得 \(\begin{cases}x=1\\y=1\\λ=2\end{cases}\)\(\begin{cases}x=-1\\y=-1\\λ=2\end{cases}\)

\(A\) \(harder\) \(case:\) 一道\(OI\)

給你\(n(n \leq 8)\)個點\(,\)你可以任意放置這些點\(,\)但需要保證每個點離原點的距離為\(r_i.\)

求這些點組成的點集中\(,\)凸包的最大面積\(.\)

\(solution :\)

列舉凸包上的點集和點的排列方式\(.\)

現在我們令凸包上的點數為\(k\),且這些點到原點的距離分別為\(r_1,r_2,...r_k\)

\(θ_i\)表示兩條相鄰的邊\(r_i\)\(r_{i\mod k + 1}\)之間的夾角

由於三角形的面積\(S = \frac{1}{2}absin(θ),\)並且所有\(θ\)的和一定是\(2π,\)所以

答案即目標函式\(F = r_1r_2sin(θ_1)+r_2r_3sin(θ_2)+...+ r_kr_1sin(θ_k).\)

限制條件\(φ = 2π - \sum\limits_{i=1}^{k} θ_i.\)

\(F - λφ = \sum\limits_{i=1}^{k}r_ir_{i\mod k+1}sin(θ_i)\) \(+\) \(λ(2π -\sum\limits_{i=1}^{k}θ_i)\)

\(θ_i\)求偏導得\(:\) \(r_ir_{i\mod k+1}cos(θ_i)=λ\)

\(λ\)求偏導得\(:\) \(2π -\sum\limits_{i=1}^{k}θ_i = 0\)

這個並不能讓我們直接解出所有的\(θ_i\)\(λ.\)

注意到\(θ_i = arccos(\frac{λ}{r_ir_{i\mod k+1}}),\)所以在\(r_i\)已經確定的情況下\(,\) \(θ_i\)是隨\(λ\)單調遞增的\(.\)

所以我們可以二分\(λ\)並算出所有的\(θ_i,\)

進而求出\(\sum\limits_{i=1}^{k}θ_i=2π\)時候所有的\(θ_i\)的值\(,\)並計算答案\(.\)

那麼這道題就做完了\(.\)

時間複雜度\(O(n!\times T),\)其中\(T\)為每次求最值時的二分次數\(.\)

\(Some\) \(Problem(s)\)

留作課後練習

\([NOI2012]\) 騎行川藏

還是推式子\(…\)

先留個坑\(,\)下次再補

\(AC\)程式碼(今天剛寫的):

#include <bits/stdc++.h>
#define db long double
using namespace std;
const int N = 10050;
int n; db E,s[N],k[N],v[N],x[N],ans = 1e6;
inline bool check(db w){
	db L,R,Mid,eps,sumE = 0,sum = 0;
	for (int i = 1; i <= n; ++i){
		if (v[i]<0) L = 0; else L = v[i];
		x[i] = -1,R = 1e18,eps = 1e-10;
		while (R - L >= eps){
			Mid = (L+R)/2;
			if (k[i] * Mid * Mid * (Mid - v[i]) <= w) x[i] = Mid,L = Mid; else R = Mid;
		}
		sumE += s[i] * k[i] * (x[i] - v[i]) * (x[i] - v[i]);
	}
	if (sumE > E) return 0;
	for (int i = 1; i <= n; ++i) sum += s[i] / x[i];
	if (sum < ans) ans = sum;
	return 1;
}
int main(){
	int i;
	ios::sync_with_stdio(0);
	cin >> n >> E;
	for (i = 1; i <= n; ++i) cin >> s[i] >> k[i] >> v[i];
	db L = 0,R = 1e18,Mid,eps = 1e-10;
	while (R - L >= eps){ Mid = (L+R)/2; if (check(Mid)) L = Mid; else R = Mid; }
	cout << fixed << setprecision(10) << ans << endl;
	return 0;
}

CF1344D Résumé Review

\(F = \sum b_i(a_i-b_i^2) + λ(\sum b_i-k)\)

\(b_i\)求偏導得 \(λ=a_i-3b_i^2\)

然後考慮二分\(λ,\)但是因為有\(b_i\leq a_i\) 的限制\(,\)所以我們算出來的\(b_i\)可能不合法\(.\)

不過不合法的部分排序後顯然是連續的一段\(,\)在外層再套一層二分即可\(.\)

然後求得了實數解\(,\)求整數解就是先令所有\(b_i=\lfloor b_i \rfloor\) 然後再排序貪心\(.\)

\(O(nlognlog(A_i))\)

\(code:\)

#include <bits/stdc++.h>
#define LL long long
#define LD long double
using namespace std;
template <typename T> void read(T &x){
	static char ch; x = 0,ch = getchar();
	while (!isdigit(ch)) ch = getchar();
	while (isdigit(ch)) x = x * 10 + ch - '0',ch = getchar();
}
inline void write(int x){if (x > 9) write(x/10); putchar(x%10+'0'); }
const int N = 100005;
LL n,k;
LL id[N],a[N]; LD b[N];
LL ans[N];
struct node{ int id; LL v; inline bool operator < (const node w) const{ return v < w.v; } }c[N]; 
int cnto;
inline int check(int p){
	LD L = -1e20,R = 1e20,Mid,kk = k,tot; int i; bool ok = 0;
	for (i = 1; i <= p; ++i) kk -= a[i];
	for (i = p+1; i <= n; ++i) L = max(L,(LD)a[i] - 3 * a[i] * a[i]),R = min(R,(LD)a[i]);
	while (R-L>1){
		Mid = (L+R)/2;
		tot = 0;
		for (i = p+1; i <= n; ++i) b[i] = sqrt((a[i]-Mid)/3),tot += b[i];
		if (tot >= kk) ok = 1,L = Mid; else R = Mid;
	}
	for (i = p+1; i <= n; ++i) b[i] = sqrt((a[i]-L)/3);
	return ok;
}
int main(){
	int i;
	read(n),read(k);
	for (i = 1; i <= n; ++i) read(c[i].v),c[i].id = i;
	sort(c+1,c+n+1);
	for (i = 1; i <= n; ++i) id[i] = c[i].id,a[i] = c[i].v;
	int L = 0,R = n,Mid,Ans = 0;
	while (L <= R){
		Mid = L+R>>1;
		for (i = 1; i <= Mid; ++i) b[i] = a[i];
		if (check(Mid)) Ans = Mid,R = Mid - 1;
		else L = Mid + 1;
	}
	for (i = 1; i <= Ans; ++i) b[i] = a[i];
	check(Ans);
	for (i = 1; i <= n; ++i) b[i] = floor(b[i]),k -= b[i];
	for (i = 1; i <= n; ++i) if (b[i] < a[i]){
		++cnto;
		c[cnto].id = i,c[cnto].v = a[i] - 3 * b[i] * b[i] - 3 * b[i];
	}
	sort(c+1,c+cnto+1);
	reverse(c+1,c+cnto+1);
	for (i = 1; i <= k; ++i) b[c[i].id] += 1;
	for (i = 1; i <= n; ++i) ans[id[i]] = b[i];
	for (i = 1; i <= n; ++i) write(ans[i]),putchar(i<n?' ':'\n');
	return 0;
}