题目:POJ2976.
题目大意:给定你n组
a
i
,
b
i
a_i,b_i
ai,bi,让你取出
n
−
k
n-k
n−k组,使得这
n
−
k
n-k
n−k组的
a
a
a之和除以
b
b
b之和最大.
1
≤
n
≤
1
0
3
,
1
≤
a
i
≤
b
i
≤
1
0
9
1\leq n\leq 10^3,1\leq a_i\leq b_i\leq 10^9
1≤n≤103,1≤ai≤bi≤109.
这是一个经典的
01
01
01分数规划模型,
01
01
01分数规划的基本式子为:
∑
i
=
1
n
a
i
∗
x
i
∑
i
=
1
n
b
i
∗
x
i
\frac{\sum_{i=1}^{n}a_i*x_i}{\sum_{i=1}^{n}b_i*x_i}
∑i=1nbi∗xi∑i=1nai∗xi
01 01 01分数规划,就是选择 x i = 0 / 1 x_i=0/1 xi=0/1,使得上式最大,并求出最大值.
解决方案一:
我们考虑猜测一个值 L L L,判定最大值 m a x max max与 L L L的大小关系,分两种情况:
情况1:
∃
∑
i
=
1
n
a
i
∗
x
i
∑
i
=
1
n
b
i
∗
x
i
≥
L
∃
∑
i
=
1
n
x
i
(
a
i
−
L
∗
b
i
)
≥
0
\exists \frac{\sum_{i=1}^{n}a_i*x_i}{\sum_{i=1}^{n}b_i*x_i}\geq L\\ \exists \sum_{i=1}^{n}x_i(a_i-L*b_i) \geq 0
∃∑i=1nbi∗xi∑i=1nai∗xi≥L∃i=1∑nxi(ai−L∗bi)≥0
发现只要这个式子成立的 L L L都是可行的,也就是说最大值大于或等于 L L L.
情况2:
∀
∑
i
=
1
n
a
i
∗
x
i
∑
i
=
1
n
b
i
∗
x
i
<
L
∀
∑
i
=
1
n
x
i
(
a
i
−
L
∗
b
i
)
<
0
\forall \frac{\sum_{i=1}^{n}a_i*x_i}{\sum_{i=1}^{n}b_i*x_i}< L\\ \forall \sum_{i=1}^{n}x_i(a_i-L*b_i)<0
∀∑i=1nbi∗xi∑i=1nai∗xi<L∀i=1∑nxi(ai−L∗bi)<0
发现这个式子成立的 L L L都是不可行的,也就是说最大值小于 L L L.
所以我们就可以二分 L L L,然后判定这个式子的最大值是否会大于 0 0 0.
判定的话找到最大的 n − k n-k n−k组 a i − L ∗ b i a_i-L*b_i ai−L∗bi,也就是预处理一下出对应的值然后排序一下就好 O ( n l o g 2 n ) O(nlog^2n) O(nlog2n).
解决方案二:
一种被称为Dinkelbach迭代法的算法.这种方法大部分情况下比二分更快,但不易于理解.
我们设一个函数:
f
(
L
)
=
−
L
∑
i
=
1
n
x
i
∗
b
i
+
∑
i
=
1
n
x
i
∗
a
i
f(L)=-L\sum_{i=1}^{n}x_i*b_i+\sum_{i=1}^{n}x_i*a_i
f(L)=−Li=1∑nxi∗bi+i=1∑nxi∗ai
将每一组
x
i
x_i
xi对应的函数图像放到直角坐标系上可以得到若干条直线:
很容易发现每一条直线与
x
x
x轴的交点的横坐标(即直线的横截距)就是最大的可行的
L
L
L值.
知道了这一点后,很容易想到我们可以先任意确定一条直线到达它的横截距位置,然后看是否有直线在目前这个位置的上方,若有则到上方某条直线的横截距位置,否则就停止输出答案.
考虑如何实现,首先我们随便取一个 L L L,一般取 0 0 0即可.然后每次用 L L L得到一组 x x x,把这一组 x x x代进去得到函数 f f f,求出 f ( L ) = 0 f(L)=0 f(L)=0时的解 L ′ L' L′.用 L ′ L' L′与 L L L比较,若 L ′ L' L′更大则用 L ′ L' L′代替 L L L继续迭代,否则将 L L L作为答案即可.
这个算法的速度不知道有没有保证,一般比二分快.
方案一代码如下:
#include<cstdio>
#include<iostream>
#include<algorithm>
using namespace std;
#define Abigail inline void
typedef long long LL;
const int N=1000;
const double eps=0.00000001;
int n,k;
double a[N+9],b[N+9],ans,c[N+9];
bool check(double mid){
double ans=0;
for (int i=1;i<=n;++i)
c[i]=a[i]-mid*b[i];
sort(c+1,c+1+n);
for (int i=n;i>k;--i)
ans+=c[i];
return ans>=0;
}
Abigail into(){
for (int i=1;i<=n;++i)
scanf("%lf",&a[i]);
for (int i=1;i<=n;++i)
scanf("%lf",&b[i]);
}
Abigail work(){
double l=0,r=1.0;
while (l+eps<r){
ans=(l+r)/2.0;
if (check(ans)) l=ans;
else r=ans;
}
}
Abigail outo(){
printf("%.0f\n",ans*100.0);
}
int main(){
while (~scanf("%d%d",&n,&k)&&n+k){
into();
work();
outo();
}
return 0;
}
方案二代码:
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cmath>
using namespace std;
#define Abigail inline void
typedef long long LL;
const int N=1000;
const double eps=0.00000001;
int n,k;
double a[N+9],b[N+9],ans;
struct node{
double v;
int id;
}c[N+9];
bool cmp(const node &a,const node &b){return a.v<b.v;}
double check(double mid){
double sa=0,sb=0;
for (int i=1;i<=n;++i)
c[i].v=a[i]-b[i]*mid,c[i].id=i;
sort(c+1,c+1+n,cmp);
for (int i=n;i>k;--i)
sa+=a[c[i].id],sb+=b[c[i].id];
return sa/sb;
}
double dinkelbach(){
double l=0,hl=-2333;
while (fabs(l-hl)>eps){
hl=l;
l=check(l);
}
return hl;
}
Abigail into(){
for (int i=1;i<=n;++i)
scanf("%lf",&a[i]);
for (int i=1;i<=n;++i)
scanf("%lf",&b[i]);
}
Abigail outo(){
printf("%.0f\n",dinkelbach()*100.0);
}
int main(){
while (~scanf("%d%d",&n,&k)&&n+k){
into();
outo();
}
return 0;
}
实测POJ上方案一跑了 94 m s 94ms 94ms,方案二跑了 47 m s 47ms 47ms.