「分數規劃」學習筆記及做題記錄

DengStar發表於2024-10-05

「分數規劃」學習筆記及做題記錄

做題時發現不會分數規劃,趕緊來學一下。

分數規劃用於求解下面一類問題:

\(n\) 個物品,第 \(i\) 個物品的價值為 \(a_i\),費用為 \(b_i\)。從中選擇若干個物品,使得價值與費用的比值 \(\dfrac{\sum a}{\sum b}\) 最大/最小。

另一種更嚴謹的表示方法是:給第 \(i\) 個物品欽定 \(w_i \in \{1, 0\}\) 表示選/不選該物品,使得 \(\dfrac{\sum_{i=1}^{n} w_i \times a_i}{\sum_{i=1}^{n} w_i \times b_i}\) 最大。

分數規劃使用二分法來求解該問題。以求最大值為例,二分比值 \(x\),求解判斷問題:是否存在方案,使比值大於等於 \(x\) (由於是實數二分,加不加等於其實無所謂)。那麼式子化為:

\[\dfrac{\sum_{i=1}^{n} w_i \times a_i}{\sum_{i=1}^{n} w_i \times b_i} \ge x \\ \Longrightarrow \sum_{i=1}^{n} w_i \times (a_i - x \times b_i) \ge 0 \]

這樣,只需求出不等號左邊式子的最大值。如果該最大值不小於 \(0\),說明比值可以達到 \(x\),否則不可以。

可以發現,這樣轉化問題的最大好處是:我們消去了除法。如果把 \(a_i - x \times b_i\) 看作第 \(i\) 個物品的新權值 \(c_i\),那麼我們只要研究如何取到這一個權值的和的最大值,而不用研究兩個權值的比,這往往能帶來極大的便利。

而分數規劃的主要難點也就在於求出新權值 \(c_i\) 的最大和。下面以幾道例題為例來講解。(由於二分的過程是類似的,下面只研究如何求出新權值的最大和)

例題

I. [USACO18OPEN] Talent Show G

問題轉化後:第 \(i\) 個物品的權值為 \(a_i\),費用為 \(w_i\),並且總費用不小於 \(W\)

可以用揹包 dp 來求解。設 \(f(i, v)\) 表示在前 \(i\) 個物品中,選擇若干個使得費用和為 \(v\) 時,權值的最大值。但這裡有一個問題:\(w_i \le 10^6\),因此 dp 陣列的第二維要開到 \(n \times w \le 2.5 \times 10^5\),這樣的時空複雜度都是無法接受的。但我們注意到,大於等於 \(W\) 的重量都可以直接視為 \(W\),這樣並不改變答案。也就是說,\(f(i, W)\) 表示的實際上是費用和不小於 \(W\),權值的最大值。轉移的時候,使用“我為人人”的轉移方法,可以使程式碼實現更簡潔。詳見程式碼。

bool check(double x)
{
	vector<double> a(n + 1);
	for(int i = 1; i <= n; i++)
		a[i] = t[i] - x * w[i];
	vector<vector<double>> f(n + 1, vector<double>(W + 1, -1e9));
	f[0][0] = 0;
	for(int i = 1; i <= n; i++)
	{
		copy(f[i - 1].begin(), f[i - 1].end(), f[i].begin()); // 如果不用滾動陣列,必須寫這一行
		for(int v = 0; v <= W; v++) // “我為人人” 
		{
			int j = min(W, v + w[i]);
			f[i][j] = max(f[i][j], f[i - 1][v] + a[i]);
		}
	}
	return f[n][W] >= 0;
}

提交記錄

II. [JSOI2016] 最佳團體

樹上揹包即可。

void dfs(int u)
{
	sz[u] = 1, f[u][1] = a[u];
	for(int v: G[u])
	{
		dfs(v);
		sz[u] += sz[v];
		for(int i = min(m, sz[u]); i > 1; i--)
		{
			for(int j = max(0, i + sz[v] - sz[u]); j <= min(i - 1, sz[v]); j++)
				f[u][i] = max(f[u][i], f[v][j] + f[u][i - j]);
		}
	}
}

bool check(double x)
{
	for(int i = 1; i <= n; i++)
		a[i] = val[i] - x * w[i];
	
	fill(f.begin(), f.end(), vector<double>(m + 1, -1e9));
	dfs(rt);
	return f[rt][m] >= 0; 
}

相關文章