参考题目:POJ2976
解析:
1.分数规划
分数规划的一般形式:
m
i
n
λ
=
f
(
x
)
=
a
(
x
)
b
(
x
)
(
x
∈
S
)
min\text{ }\lambda=f(\text{x})=\frac{a(\text{x})}{b(\text{x})}(x\in S)
min λ=f(x)=b(x)a(x)(x∈S)
其中,解向量
x
\text{x}
x在解空间
S
S
S内,
a
(
x
)
,
b
(
x
)
a(x),b(x)
a(x),b(x)都是连续的实值函数。
设
λ
∗
=
f
(
x
∗
)
\lambda^*=f(x^*)
λ∗=f(x∗)为该规划最优解,有
λ
∗
=
f
(
x
∗
)
=
a
(
x
∗
)
b
(
x
∗
)
\lambda^*=f(x^*)=\frac{a(x^*)}{b(x^*)}
λ∗=f(x∗)=b(x∗)a(x∗)
则
λ
∗
∗
b
(
x
∗
)
=
a
(
x
∗
)
\lambda^**b(x^*)=a(x^*)
λ∗∗b(x∗)=a(x∗)
于是,我们有
a
(
x
∗
)
−
λ
∗
∗
b
(
x
∗
)
=
0
a(x^*)-\lambda^**b(x^*)=0
a(x∗)−λ∗∗b(x∗)=0
我们构造函数 g ( λ ) = m i n ( a ( x ) − λ ∗ b ( x ) ) g(\lambda)=min\text{ }(a(x)-\lambda*b(x))\text{ } g(λ)=min (a(x)−λ∗b(x))
证明其满足单调性:
即证明对于
∀
λ
1
<
λ
2
\forall\lambda_1<\lambda_2
∀λ1<λ2,都有
g
(
λ
1
)
>
g
(
λ
2
)
g(\lambda_1)>g(\lambda_2)
g(λ1)>g(λ2)。
证明:
设当
g
(
λ
1
)
g(\lambda_1)
g(λ1)取到最小值时,
x
=
x
1
\text{x}=\text{x}_1
x=x1
g
(
λ
1
)
=
min
x
∈
S
(
a
(
x
)
−
λ
1
∗
b
(
x
)
)
g(\lambda_1)=\min_{\text{x}\in S}(a(\text{x})-\lambda_1*b(\text{x}))
g(λ1)=x∈Smin(a(x)−λ1∗b(x))
=
a
(
x
1
)
−
λ
1
∗
b
(
x
1
)
=a(\text{x}_1)-\lambda_1*b(\text{x}_1)
=a(x1)−λ1∗b(x1)
>
a
(
x
1
)
−
λ
2
∗
b
(
x
1
)
>a(\text{x}_1)-\lambda_2*b(\text{x}_1)
>a(x1)−λ2∗b(x1)
≥
min
x
∈
S
(
a
(
x
)
−
λ
2
∗
b
(
x
)
)
\geq\min_{\text{x}\in S}(a(\text{x})-\lambda_2*b(\text{x}))
≥x∈Smin(a(x)−λ2∗b(x))
=
g
(
λ
2
)
=g(\lambda_2)
=g(λ2)
由此看来,我们就可以二分 λ \lambda λ然后计算 g ( λ ) g(\lambda) g(λ),不断逼近最终的准确解。
然而,我们还有一种快得多的方法
接下来,隆重介绍
D
i
n
k
e
l
b
a
c
h
Dinkelbach
Dinkelbach迭代法:
D i n k e l B a c h DinkelBach DinkelBach定理
定理内容:设原规划解为
λ
∗
\lambda^*
λ∗,则上述函数
g
(
λ
)
=
0
g(\lambda)=0
g(λ)=0当且仅当
λ
=
λ
∗
\lambda=\lambda^*
λ=λ∗。
是不是很简短?(然而接下来并不会用)
考虑每一次二分出的答案
λ
\lambda
λ和验证出的函数
g
(
λ
)
g(\lambda)
g(λ)能不能再被利用。
考虑对于一个确定的向量
x
\text{x}
x,我们考虑函数
f
x
(
λ
)
=
a
(
x
)
−
λ
∗
b
(
x
)
f_x(\lambda)=a(x)-\lambda*b(x)
fx(λ)=a(x)−λ∗b(x),显然这是一个一次函数(废话)。那么这个函数的横截距。。。是不是就可以利用一下。
于是我们每次将答案更新至新的横截距处,继续验证新的函数的横截距,要是横截距相等.。。
那么我们就已经找到最优
x
x
x向量使得
f
x
f_x
fx的横截距最大,因为已经不能继续靠近真实值了。
即我们已经找到最优的答案
2,0/1分数规划
看懂上面那个这个自然就会了。
UPDATE:
今天班长又来问我 0 / 1 0/1 0/1分数规划,感觉很多人可能存在一个误区,这里略作解释。
规划问题有两种,求解最大值或最小值,而这两种的区别不是在于上下界的调整或者是什么其他的问题。
其实我们最终的目的就是求解
g
(
λ
∗
)
=
0
g(\lambda^*)=0
g(λ∗)=0
求解这两种问题的二分形式是一样的,对于二分到的解,我们会验证得出一个
g
(
λ
)
g(\lambda)
g(λ),我们调整二分上下界的依据就是这个
g
(
λ
)
g(\lambda)
g(λ)是否大于
0
0
0。
1、
g
(
λ
)
>
0
g(\lambda)>0
g(λ)>0的情况。求最大值的时候说明我们可以得到一个更大的解,求最小值的时候说明实际规划中不可能得到当前解,结论都是下界上移,
2、
g
(
λ
)
<
0
g(\lambda)<0
g(λ)<0的情况。求最小值的时候说明我们可以得到一个更小的解,求最大值的时候说明实际规划中不可能得到当前解,结论:上界下移。
所以二分的构架是一样的,区别只是在于
c
h
e
c
k
check
check里面的东西。
c
h
e
c
k
check
check才是体现究竟求最大值还是最小值的地方。
但是用 D i n k e l B a c h DinkelBach DinkelBach就不会有这种问题。。。
代码(二分):
#include<cstdio>
#include<cctype>
#include<algorithm>
using namespace std;
#define ll long long
#define re register
#define gc getchar
#define pc putchar
#define cs const
#define st static
inline
ll getint(){
re ll num=0;
re char ch;
for(ch=gc();!isdigit(ch);ch=gc());
for(num=0;isdigit(ch);ch=gc())num=(num<<1)+(num<<3)+(ch^48);
return num;
}
inline
double max(cs double &a,cs double &b){
return a<b?b:a;
}
inline
double min(cs double &a,cs double &b){
return a<b?a:b;
}
ll a[1002],b[1002];
int n,k;
double c[1002];
inline
bool check(double x){
double ans=0.0;
for(int re i=1;i<=n;++i){
c[i]=(double)a[i]-b[i]*x;
}
sort(c+1,c+n+1);
for(int re i=n;i>k;--i){
ans+=c[i];
}
return ans>0;
}
int main(){
while(true){
n=getint();
k=getint();
if(n==0&&k==0)return 0;
for(int re i=1;i<=n;++i)a[i]=getint();
for(int re i=1;i<=n;++i)b[i]=getint();
double l=0,r=1.0;
while(l+1e-4<r){
double mid=(l+r)/2;
if(check(mid))l=mid;
else r=mid;
}
printf("%.0lf\n",l*100);
}
return 0;
}
代码( D i n k e l b a c h Dinkelbach Dinkelbach迭代):
#include<cstdio>
#include<cctype>
#include<algorithm>
#include<cmath>
using namespace std;
#define ll long long
#define re register
#define gc getchar
#define pc putchar
#define cs const
#define st static
inline
ll getint(){
re ll num=0;
re char ch;
for(ch=gc();!isdigit(ch);ch=gc());
for(num=0;isdigit(ch);ch=gc())num=(num<<1)+(num<<3)+(ch^48);
return num;
}
struct node{
int a,b;
double d;
friend bool operator<(cs node &a,cs node &b){
return a.d<b.d;
}
};
node q[1001];
int n,k;
double ans,L;
int main(){
while(true){
n=getint();
k=getint();
if(n==0&&k==0)return 0;
for(int re i=1;i<=n;++i)q[i].a=getint();
for(int re i=1;i<=n;++i)q[i].b=getint();
while(true){
L=ans;
for(int re i=1;i<=n;++i)q[i].d=(double)q[i].a-L*q[i].b;
sort(q+1,q+n+1);
double x=0,y=0;
for(int re i=n;i>k;--i){
x+=q[i].a;
y+=q[i].b;
}
ans=x/y;
if(fabs(ans-L)<=1e-4)break;
}
printf("%.0lf\n",ans*100);
}
return 0;
}