0/1分数规划

0/1分数规划

二分与0/1分数规划

0/1分数规划问题:

给定两个都包含 n 个数的正整数序列 {a_1,a_2,...,a_n}
{b_1,b_2,...,b_n} ,同时选出 kab ,求 \max\frac{\sum_{i=1}^{n}a_is_i }{\sum_{i=1}^{n}b_is_i} ,其中 \huge{s_i\in [0,1]} ,表示选不选 a_ib_i ,且 \huge{\sum_{i=1}^{n}s_i=k}

如果使用暴力枚举,对 n 个数进行排列组合,时间复杂度为 O(2^n) ,显然不行。为什么是 O(2^n) ?因为每个数有两种可能,共 n 个数,所以为 O(n)

这样的复杂度显然是不可取的!为了加快速度,可以用猜数的方法,猜测一个数 x (很像在 100 内猜一个数),使

\huge{\frac{\sum_{i=1}^{n}a_is_i}{\sum_{i=1}^{n}b_is_i}\ge x}

移项得: \huge{f=\sum_{i=1}^{n}(a_i-xb_i)s_i\ge x}

y=a_i-xb_i ,把 xy 看作变量,这是一条直线。原问题转化为在 n 条直线中选 k 条直线求和。这 n 条直线如图1所示。

一条直线 y_ix 轴的交点(横截距)等于单独选这条直线的最大值 {\large \frac{a_i}{b_i}}

用直线理解0/1分数规划问题,非常直观易懂。选 k 对数的 f 值等于选 k 条直线所组合的 y 值之和,当 f=0 ,是 fx 轴的交点。选不同的 k 条直线会有不同的结果,最大时 x=M

  1. k=1 ,选一对数,求出 \huge{\max\frac{a_i}{b_i},i\in[1,n]} 即可。
  2. k=2 ,选两对数,在所有使 y_i+y_j=0x 中,最大的就是答案 $M$。如果用暴力确定 M ,有 C_n^2 中可能。

看到这里,我们可以发现,暴力枚举是不可取的,复杂度增长很快。这时要使用二分法

二分的初始区间[L,R]L=0,R 是大于答案的值,如设为 \sum_{i=1}^{n}a_i

步骤:

x=R 开始,计算所有的 y_i=a_i-xb_i ,排序,求 f=\sum_{i=1}^{k}y_i ,若 f<0 ,说明 x 小了;反之大了。

洛谷P10505 Dropping Test是一道模板题。代码中做了 50 次二分,执行一次 check() 函数复杂度为 O(nlog_2n)

Dropping Test

题目描述

在某个课程中,你需要进行 n 次测试。

如果你在共计 b_i 道题的测试 i 上的答对题目数量为 a_i ,你的累积平均成绩就被定义为

100\times \dfrac{\displaystyle \sum_{i=1}^n a_i}{\displaystyle \sum_{i=1}^n b_i}

给定您的考试成绩和一个正整数 k ,如果您被允许放弃任何 k 门考试成绩,您的累积平均成绩的可能最大值是多少。

假设您进行了 3 次测试,成绩分别为 5/5,0/12/6

在不放弃任何测试成绩的情况下,您的累积平均成绩是

100\times \frac{5+0+2}{5+1+6}=50

然而,如果你放弃第三门成绩,则您的累积平均成绩就变成了

100\times \frac{5+0}{5+1}\approx 83.33\approx 83

输入格式

输入包含多组测试用例,每个测试用例包含三行。

对于每组测试用例,第一行包含两个整数 nk

第二行包含 n 个整数,表示所有的 a_i

第三行包含 n 个整数,表示所有的 b_i

当输入用例 n=k=0 时,表示输入终止,且该用例无需处理。

输出格式

对于每个测试用例,输出一行结果,表示在放弃 k 门成绩的情况下,可能的累积平均成绩最大值。

结果应四舍五入到最接近的整数。

样例 #1

样例输入 #1

3 1
5 0 2
5 1 6
4 2
1 2 7 9
5 6 7 9
0 0

样例输出 #1

83
100

提示

数据范围 1 \le n \le 1000, 0 \le k < n, 0 \le a_i \le b_i \le 10^9

代码如下:


bool check(double x){
	for(int i=0;i<n;i++){
		p[i].y=p[i].a*1.0-x*p[i].b;
	}
	sort(p,p+n);
	double f=0;
	for(int i=0;i<k;i++){
		f+=p[i].y;
	}
	return f<0;
}

	while(scanf("%d%d",&n,&k)==2&&n+k){
		k=n-k;
		for(int i=0;i<n;i++){
			scanf("%d",&p[i].a);
		}
		for(int i=0;i<n;i++){
			scanf("%d",&p[i].b);
		}
		double l=0,r=0;
		for(int i=0;i<n;i++){
			r+=p[i].a;
		}
		for(int i=0;i<50;i++){
			double mid=l+(r-l)/2;
			if(check(mid)){
				r=mid;//线在M右侧,左移。
			}else{
				l=mid;//线在M左侧,右移。
			}
		}
		printf("%d\n",(int)(100*(l+0.005)));//四舍五入。
	}

0/1分数规划的应用

0/1分数规划是一种简单的数学模型,常与一些特定的场景结合出题,如:

  1. 最优比率背包,与0/1背包结合。
  2. 最优比率生成树,与生成树结合。

洛谷P4377 [USACO18OPEN] Talent Show G就是一道最优比率背包的题目。

本题用0/1背包建模:有 n 个物品,物品 i 的重量为 w_i ,价值为 t_i ,选出一些物品,要求总重量大于等于 W ,求最大价值。

本题和基本的0/1分数规划很像,目标 \huge{\frac{\sum_{i=1}^{n}t_is_i}{\sum_{i=1}^{n}w_is_i}(s_i\in[0,1])} ,只是结束条件为 \huge{\sum_{i=1}^{n}w_is_i\ge W}

代码很简单:

int n,W;
struct Cow{
	int w,t;
	double y;
}a[maxn];
double f[maxm];
bool check(double x){
	for(int i=1;i<=n;i++){
		a[i].y=(double)a[i].t-x*a[i].w;
	}
    //0/1背包模板。
	for(int i=1;i<=W;i++){
		f[i]=-inf;
	}
	f[0]=0;
	for(int i=1;i<=n;i++){
		for(int j=W;j>=0;j--){
			if(j+a[i].w>=W){
				f[W]=max(f[W],f[j]+a[i].y);
			}else{
				f[j+a[i].w]=max(f[j+a[i].w],f[j]+a[i].y);
			}
		}
	}
	return f[W]<0;
}

【习题】

Let`s have a test! :smiley:

1 个赞

请不要发AC代码,你可以发核心代码

:ok_hand: :ok_hand:

不是,你这和发了AC代码有什么区别

???

只放了check是吧,当我没说