首页 > 其他分享 >01 分数规划

01 分数规划

时间:2024-06-02 09:32:55浏览次数:9  
标签:分数 01 int sum mid times Maxn 规划

1 问题概述

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

给定两个数列 \(a_i,b_i\),求出一个数列 \(w_i\in \{0,1\}\),最小(大)化下列式子:

\[\dfrac{\sum\limits_{i=1}^na_i\times w_i}{\sum\limits_{i=1}^nb_i\times w_i} \]

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

2 解法

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

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

\[\dfrac{\sum a_i\times w_i}{\sum b_i\times w_i}>mid\\ \sum a_i\times w_i-mid\times\sum b_i\times w_i>0\\ \sum w_i\times (a_i-mid\times b_i)>0 \]

所以我们只需要求出 \(\sum w_i\times (a_i-mid\times b_i)\) 的最大值,如果比 \(0\) 大就代表可行,否则一定不可行。

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

3 实例

3.1 [USACO18OPEN] Talent Show G

首先转化题目模型如下:

求出 \(\dfrac{\sum t_i}{\sum w_i}\) 的最大值,要满足 \(\sum w_i\ge W\)。

发现要求式子就是 01 分数规划的标准形式,现在问题是怎样求 \(\sum (t_i-mid\times w_i)\) 最大值,还需要保证 \(\sum w_i\ge W\)。

仔细思考一下我们不难发现,如果我们构造一个物品,体积为 \(w_i\),价值为 \((t_i-mid\times w_i)\),这个问题就转化为了一个相当朴素的 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] 最小圈

首先转化模型如下:

每一条边上有两个权值 \(w_i,b_i\),且 \(b_i=1\)。求出一个环使得 \(\dfrac{\sum w_i}{\sum b_i}\) 最小。

显然这又是一个 01 分数规划问题,我们最后转化出的式子应该是 \(\sum(w_i-mid\times b_i)<0\),也就是 \(\sum(w_i-mid)<0\)。

我们将边权赋为 \((w_i-mid)\),然后就是要求一个环使得边权和为负。直接利用 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;
}

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

标签:分数,01,int,sum,mid,times,Maxn,规划
From: https://www.cnblogs.com/dzbblog/p/18226795

相关文章

  • ACWing算法基础课刷题记录2024-06-01--2day
    831.KMP字符串给定一个字符串 S......
  • CSP历年复赛题-P1981 [NOIP2013 普及组] 表达式求值
    原题链接:https://www.luogu.com.cn/problem/P1981题意解读:中缀表达式求值,只有+,*,没有括号,保留后4位。解题思路:中缀表达式求值的典型应用,采用两个栈:符号栈、数字栈,对于没有括号的情况,只需要如下步骤:1、遍历表达式每一个字符2、如果遇到数字,则持续提取数字,保存整数到数字栈3、......
  • 01:Java概述及基本语法
    1、Java是什么?是SUN(StanfordUniversityNetwork,斯坦福大学网络公司)1995年推出的一门高级编程语言2、Java技术体系平台JavaSE(JavaStandardEdition)标准版JavaEE(JavaEnterpriseEdition)企业版JavaME(JavaMicroEdition)小型版3、Java主要特性面向对象......
  • 今日指数day01学习笔记
    1、项目概述    该项目是基于股票实时交易的数据分析产品,为用户和机构提供个性化的股票数据分析和展示服务    核心功能:数据分析和展示为主,功能涵盖了A股大盘实时指数展示、涨幅榜、个股涨跌、个股秒级行情、实时日K线行情等2、股票相关名词    股......
  • 使用动态规划法求最大连续子序列和
    通过动态规划方法求最大连续子序列和问题描述:给定一个有n(n>=1)个整数的序列,求出其中最大连续子序列的和。如:{-2,11,-4,13,-5,-2},最大的连续子序列是:{11,-4,13}和为20。【规定】一个序列的最大连续子序列和至少是0,如果小于0,其结果为0。解法:使用一个整型数组arr[]来存......
  • 01_Zotero插件安装
    Zotero插件安装目录页1.ZoteroStyle插件使用说明1.1.期刊标签、影响因子不显示?2.zotero-better-notes插件安装及使用说明2.1.笔记模板(采用HTML代码控制)2.2.笔记样式(采用CSS代码控制)2.2.1.我现在使用的CSS样式0.1.插件安装找到插件的对应地址......
  • 01_Zotero软件安装
    Zotero软件安装目录页1.Zotero软件安装问题1.1.各种版本软件安装地址1.2.Zotero7(beta版)安装的喜与悲2.软件使用问题2.1.无法加载与文字处理器通信所需的组件--Word中Zotero组件失效2.2.Word中建立Zotero超链接--跳转到参考文献1.Zotero软件安装......
  • 【NOIP2018普及组复赛】题1:标题统计
    题1:标题统计题目描述凯凯刚写了一篇美妙的作文,请问这篇作文的标题中有多少个字符?注意:标题中可能包含大、小写英文字母、数字字符、空格和换行符。统计标题字符数时,空格和换行符不计算在内。【输入格式】输入文件只有一行,一个字符串......
  • 1-006 连续因子(分数 20,c++)
    一个正整数 N 的因子中可能存在若干连续的数字。例如630可以分解为3×5×6×7,其中5、6、7就是3个连续的数字。给定任一正整数 N,要求编写程序求出最长连续因子的个数,并输出最小的连续因子序列。输入格式:输入在一行中给出一个正整数 N(1<N<231)。输出格式:首先在第1......
  • HTML网页设计:基于爱护动物题材——保护动物大象(6页) HTML网页设计结课作业 web课程设
    ......