分数规划

“空即是色,色即是空。”

用法

分数规划求一个分式的极值。

给出 $a_i$ 和 $b_i$ ,求一组 $w_i\in\{0,1\}$ ,最大化/最小化:

当然,毒瘤题中还会有一些特殊限制。

求解

二分

令当前答案为 $mid$ ,我们只需要使:(以最大值为例)

然后推得:

即可。最小值类似。

Dinkelbach 算法

Dinkelbach 算法的大概思想是每次用上一轮的答案当做新的 $L$ 来输入,不断地迭代,直至答案收敛。说实话,这东西我看了有 $5$ 篇讲解也不是太懂,之后有时间再学学。

有点类似于构造函数,通过对已知答案的迭代来得出当前答案。并根据更优解移动答案以逼近最优解。

例题

Talent Show

求最大分数,且需要满足 $\sum w_i\times b_i\ge W$ 的条件。

其实在开始的时候,就会发现 $w_i\in\{0,1\}$ 十分熟悉,有些像…… $01$ 背包。

把 $b_i$ 作为 $i$ 物品的重量,将 $a_i-mid\times b_i$ 作为第 $i$ 个物品的价值,作为 dp 来转移,求出 $dp[n][W]$ 即可。

将大于 $W$ 的 $\sum w_i\times b_i$ 直接视作 $W$ 即可。因为我们并不考虑实际重量和,只关心是否能把背包装满。

AC Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<ctime>
#include<iomanip>
#include<queue>
#include<stack>
#include<map>
#include<vector>
#define gh() getchar()
#define re register
typedef long long ll;
using namespace std;
const int MAXN=301;
const int MAXW=1001;
const double eps=1e-5;
template<class T>
inline void read(T &x)
{
x=0;
char ch=gh(),t=0;
while(ch<'0'||ch>'9') t|=ch=='-',ch=gh();
while(ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+(ch^48),ch=gh();
if(t) x=-x;
}
template<class T,class ...T1>
inline void read(T &x,T1 &...x1)
{
read(x),read(x1...);
}
template<class T>
inline bool checkMax(T &x,T &y)
{
return x<y?x=y,1:0;
}
template<class T>
inline bool checkMin(T &x,T &y)
{
return x>y?x=y,1:0;
}
int a[MAXN],b[MAXN],W,N;
double dp[MAXW];
inline bool check(double x)
{
for(int i=1;i<=W;++i) dp[i]=-1e9;
for(int i=1;i<=N;++i)
for(int j=W;j>=0;--j)
{
int k=min(W,j+b[i]);
dp[k]=max(dp[k],dp[j]+a[i]-x*b[i]);
}
return dp[W]>0;
}
int main()
{
// freopen("frac-programming.in","r",stdin);
// freopen("frac-programming.out","w",stdout);
read(N,W);
for(int i=1;i<=N;++i) read(b[i],a[i]);
double l=0,r=1e9;
while(r-l>eps)
{
double mid=(l+r)/2;
if(check(mid)) l=mid;
else r=mid;
}
printf("%.0lf",floor(1000*l));
return 0;
}
/*
3 15
20 21
10 11
30 31
*/