0/1分数规划 模型描述
0/1分数规划的模型是这样的
给定一系列整数
a
1
,
a
2
,
.
.
.
,
a
n
a1,a2,...,an
a1,a2,...,an以及
b
1
,
b
2
,
.
.
.
,
b
n
b1,b2,...,bn
b1,b2,...,bn
求解一组
x
i
(
1
<
=
i
<
=
n
,
x
i
=
0
或
1
)
xi(1<=i<=n,xi=0或1)
xi(1<=i<=n,xi=0或1) 使得下式最大化
∑
i
=
1
n
a
i
∗
x
i
∑
i
=
1
n
b
i
∗
x
i
\frac{\sum_{i=1}^na_i*x_i}{\sum_{i=1}^nb_i*x_i}
∑i=1nbi∗xi∑i=1nai∗xi
0/1分数规划 求解
求解0/1分数规划一个通常的做法是二分答案
即可以在值域[L,R]内二分
m
i
d
mid
mid
判定是否存在一组解
x
i
xi
xi使得
∑
i
=
1
n
a
i
∗
x
i
∑
i
=
1
n
b
i
∗
x
i
>
=
m
i
d
\frac{\sum_{i=1}^na_i*x_i}{\sum_{i=1}^nb_i*x_i}>=mid
∑i=1nbi∗xi∑i=1nai∗xi>=mid
若是则说明二分值
m
i
d
mid
mid比能求得的最大值小,令
L
=
m
i
d
L=mid
L=mid
否则说明二分值
m
i
d
mid
mid大于能求得的最大值,令
R
=
m
i
d
R=mid
R=mid
二分直至达到精度要求
那么如何判断上述式子是否成立呢
我们试着做如下变换
∑
i
=
1
n
a
i
∗
x
i
∑
i
=
1
n
b
i
∗
x
i
>
=
m
i
d
\frac{\sum_{i=1}^na_i*x_i}{\sum_{i=1}^nb_i*x_i}>=mid
∑i=1nbi∗xi∑i=1nai∗xi>=mid
(
∑
i
=
1
n
a
i
∗
x
i
)
−
m
i
d
∗
(
∑
i
=
1
n
b
i
∗
x
i
)
>
=
0
(\sum_{i=1}^na_i*x_i)-mid*(\sum_{i=1}^nb_i*x_i)>=0
(i=1∑nai∗xi)−mid∗(i=1∑nbi∗xi)>=0
∑
i
=
1
n
(
a
i
−
m
i
d
∗
b
i
)
∗
x
i
>
=
0
\sum_{i=1}^n(a_i-mid*b_i)*x_i>=0
i=1∑n(ai−mid∗bi)∗xi>=0
到这里判断方法已经很显然了
若
a
i
−
m
i
d
∗
b
i
<
0
ai-mid*bi < 0
ai−mid∗bi<0 就令
x
i
=
0
xi=0
xi=0,否则
x
i
=
1
xi=1
xi=1
一次判断最多只用
O
(
n
)
O(n)
O(n)的遍历
二分的复杂度约为
O
(
l
o
g
n
)
O(logn)
O(logn)
所以整体的渐进复杂度为
O
(
n
l
o
g
n
)
O(nlogn)
O(nlogn)
0/1分数规划 应用
POJ2976 Dropping tests
入门裸题
每次二分时把
n
n
n组
a
i
−
m
i
d
∗
b
i
a_i-mid*b_i
ai−mid∗bi都算出来
从大到小排序
检查前
n
−
k
n-k
n−k组的和是否大于等于0
若是则
L
=
m
i
d
L=mid
L=mid,否则
R
=
m
i
d
R=mid
R=mid
因为有
a
i
<
=
b
i
a_i<=b_i
ai<=bi
所以在
[
0
,
1
]
[0,1]
[0,1]内二分即可
#include<iostream>
#include<vector>
#include<algorithm>
#include<queue>
#include<cstring>
#include<cstdio>
using namespace std;
typedef double dd;
int read()
{
int f=1,x=0;
char ss=getchar();
while(ss<'0'||ss>'9'){if(ss=='-')f=-1;ss=getchar();}
while(ss>='0'&&ss<='9'){x=x*10+ss-'0';ss=getchar();}
return f*x;
}
const int maxn=1010;
int n,k;
int a[maxn],b[maxn];
dd tt[maxn];
dd ans;
int check(dd x)
{
dd sum=0;
for(int i=1;i<=n;++i)
tt[i]=a[i]-x*b[i];//每组ai-mid*bi
sort(tt+1,tt+1+n);
for(int i=n;i>=k+1;--i) sum+=tt[i];//检查前n-k组的和
return sum>=0;
}
int main()
{
while(scanf("%d%d",&n,&k)!=EOF)
{
if(n==0&&k==0) break;
for(int i=1;i<=n;++i)a[i]=read();
for(int i=1;i<=n;++i)b[i]=read();
dd L=0,R=1,mid;
while(R-L>1e-4)//注意精度要求
{
mid=(L+R)/2;
if(check(mid))L=mid;
else R=mid;
}
printf("%.0lf\n",L*100);
}
return 0;
}
POJ - 2728 Desert King【最优比率生成树】题解
POJ - 3621 Sightseeing Cows【最优比率环】题解
BZOJ4819 || 洛谷P3705 [SDOI2017]新生舞会【0/1分数规划+费用流】题解