「分数规划」学习笔记及做题记录

「分数规划」学习笔记及做题记录

做题时发现不会分数规划,赶紧来学一下。

分数规划用于求解下面一类问题:

n 个物品,第 i 个物品的价值为 ai,费用为 bi。从中选择若干个物品,使得价值与费用的比值 ab 最大/最小。

另一种更严谨的表示方法是:给第 i 个物品钦定 wi{1,0} 表示选/不选该物品,使得 i=1nwi×aii=1nwi×bi 最大。

分数规划使用二分法来求解该问题。以求最大值为例,二分比值 x,求解判断问题:是否存在方案,使比值大于等于 x (由于是实数二分,加不加等于其实无所谓)。那么式子化为:

i=1nwi×aii=1nwi×bixi=1nwi×(aix×bi)0

这样,只需求出不等号左边式子的最大值。如果该最大值不小于 0,说明比值可以达到 x,否则不可以。

可以发现,这样转化问题的最大好处是:我们消去了除法。如果把 aix×bi 看作第 i 个物品的新权值 ci,那么我们只要研究如何取到这一个权值的和的最大值,而不用研究两个权值的比,这往往能带来极大的便利。

而分数规划的主要难点也就在于求出新权值 ci 的最大和。下面以几道例题为例来讲解。(由于二分的过程是类似的,下面只研究如何求出新权值的最大和)

例题

I. [USACO18OPEN] Talent Show G

问题转化后:第 i 个物品的权值为 ai,费用为 wi,并且总费用不小于 W

可以用背包 dp 来求解。设 f(i,v) 表示在前 i 个物品中,选择若干个使得费用和为 v 时,权值的最大值。但这里有一个问题:wi106,因此 dp 数组的第二维要开到 n×w2.5×105,这样的时空复杂度都是无法接受的。但我们注意到,大于等于 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; 
}
posted @   DengStar  阅读(17)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示