【模板】0/1分数规划

参考题目: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)(xS)
其中,解向量 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 &lt; λ 2 \forall\lambda_1&lt;\lambda_2 λ1<λ2,都有 g ( λ 1 ) &gt; g ( λ 2 ) g(\lambda_1)&gt;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)=xSmin(a(x)λ1b(x)) = a ( x 1 ) − λ 1 ∗ b ( x 1 ) =a(\text{x}_1)-\lambda_1*b(\text{x}_1) =a(x1)λ1b(x1) &gt; a ( x 1 ) − λ 2 ∗ b ( x 1 ) &gt;a(\text{x}_1)-\lambda_2*b(\text{x}_1) >a(x1)λ2b(x1) ≥ min ⁡ x ∈ S ( a ( x ) − λ 2 ∗ b ( x ) ) \geq\min_{\text{x}\in S}(a(\text{x})-\lambda_2*b(\text{x})) xSmin(a(x)λ2b(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 ( λ ) &gt; 0 g(\lambda)&gt;0 g(λ)>0的情况。求最大值的时候说明我们可以得到一个更大的解,求最小值的时候说明实际规划中不可能得到当前解,结论都是下界上移,
2、 g ( λ ) &lt; 0 g(\lambda)&lt;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;
}
  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值