01 分数规划

1 问题概述

分数规划是用于求一类分式的极值问题。

给定两个数列 ai,bi,求出一个数列 wi{0,1},最小(大)化下列式子:

i=1nai×wii=1nbi×wi

再说直白点就是每个物品有 a,b 两个权值,选出当中一些物品,使 ab 最小(大)。

2 解法

解决 01 分数规划问题的通法就是二分。下面以最大值举例说明。

我们二分一个答案 mid,如果要求的分式值可以大于这个 mid 那么就说明可行,转移二分区间并记录答案。于是我们就可以推出下面式子:

ai×wibi×wi>midai×wimid×bi×wi>0wi×(aimid×bi)>0

所以我们只需要求出 wi×(aimid×bi) 的最大值,如果比 0 大就代表可行,否则一定不可行。

下面举一些例子来说明 01 分数规划的题目的特征。

3 实例

3.1 [USACO18OPEN] Talent Show G

首先转化题目模型如下:

求出 tiwi 的最大值,要满足 wiW

发现要求式子就是 01 分数规划的标准形式,现在问题是怎样求 (timid×wi) 最大值,还需要保证 wiW

仔细思考一下我们不难发现,如果我们构造一个物品,体积为 wi,价值为 (timid×wi),这个问题就转化为了一个相当朴素的 dp —— 01 背包。所以我们直接套在二分中即可。

代码:

#include <bits/stdc++.h>
#define int long long

using namespace std;

const int Maxn = 2e5 + 5;
const int Inf = 2e9;
const int base = 10000;

int n, W;
int w[Maxn], t[Maxn];

int sum = 0, sw = 0;

int dp[255][2505];
bool check(int x) {
	memset(dp, 128, sizeof dp);
	dp[0][0] = 0;
	for(int i = 0; i < n; i++) {
		for(int j = 0; j <= W; j++) {
			int k = min(W, j + w[i + 1]);
			dp[i + 1][k] = max(dp[i + 1][k], dp[i][j] + t[i + 1] - x * w[i + 1]);
			dp[i + 1][j] = max(dp[i + 1][j], dp[i][j]);
		}
	}
	return dp[n][W] > 0;
}

int ans = 0;
void bs() {
	int l = 0, r = sum, mid;
	while(l <= r) {
		mid = (l + r) >> 1;
		if(check(mid)) {
			ans = mid;
			l = mid + 1;
		}
		else {
			r = mid - 1;
		}
	}
}

signed main() {
	ios::sync_with_stdio(0);
	cin.tie(0), cout.tie(0);
	cin >> n >> W;
	for(int i = 1; i <= n; i++) {
		cin >> w[i] >> t[i];
		t[i] *= base; 
		sum += t[i];
		sw += w[i];
	}
	bs();
	cout << ans / 10;
	return 0;
}

3.2 [HNOI2009] 最小圈

首先转化模型如下:

每一条边上有两个权值 wi,bi,且 bi=1。求出一个环使得 wibi 最小。

显然这又是一个 01 分数规划问题,我们最后转化出的式子应该是 (wimid×bi)<0,也就是 (wimid)<0

我们将边权赋为 (wimid),然后就是要求一个环使得边权和为负。直接利用 SPFA 判负环即可。

代码如下:

#include <bits/stdc++.h>

using namespace std;

const int Maxn = 2e5 + 5;
const int Inf = 2e9;

int n, m;
struct Edge {
	int u, v;
	double w;
}ed[Maxn];

int head[Maxn], edgenum;
struct node {
	int nxt, to;
	double w;
}edge[Maxn];

void add(int u, int v, double w) {
	edge[++edgenum] = {head[u], v, w};
	head[u] = edgenum;
}

double dis[Maxn];
bool vis[Maxn];
bool SPFA(int x) {
	vis[x] = 1;
	for(int i = head[x]; i; i = edge[i].nxt) {
		int to = edge[i].to;
		double w = edge[i].w;
		if(dis[x] + w < dis[to]) {
			dis[to] = dis[x] + w;
			if(vis[to] || SPFA(to)) {
				return 1;
			}
		}
	} 
	vis[x] = 0;
	return 0;
}

int check(double x) {
	edgenum = 0;
	for(int i = 1; i <= n; i++) {
		head[i] = 0;
	}
	for(int i = 1; i <= m; i++) {
		add(ed[i].u, ed[i].v, ed[i].w - x);
	}
	for(int i = 1; i <= n; i++) {
		dis[i] = 0;
		vis[i] = 0;
	}
	for(int i = 1; i <= n; i++) {
		if(SPFA(i)) {
			return 1;
		}
	}
	return 0;
}

double bs() {
	double l = -1e7, r = 1e7, mid;
	while(r - l >= 1e-9) {
		mid = (l + r) / 2.0;
		if(check(mid)) {
			r = mid;
		}
		else {
			l = mid;
		}
	}
	return l;
}

int main() {
	ios::sync_with_stdio(0);
	cin.tie(0), cout.tie(0);
	cin >> n >> m;
	for(int i = 1; i <= m; i++) {
		cin >> ed[i].u >> ed[i].v >> ed[i].w;
		add(ed[i].u, ed[i].v, ed[i].w);
	}
	double res = bs();
	cout << fixed << setprecision(8) << res ;
	return 0;
}

通过上述实例不难发现,当题目中出现两个数列和之比,也就是形如 aibi 的时候,我们往往考虑 01 分数规划。当我们通过二分将问题转化为 (aimid×bi) 的最值与 0 的关系之后,主要的问题就在于怎样求 (aimid×bi) 的最值了。

posted @   UKE_Automation  阅读(29)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示