01分数规划
问题:给定一个正整数m和两个正数数组a[n]和b[n]。对于数组x[n],x[n]中有m个i使得x[i] = 0,其余x[i] = 1,并且令R = segma(a[i]*x[i]) / segma(b[i]*x[i])。求一个满足条件的x[n],使得R的值最大,输出R的最大值。
分析:构造函数F(L) = segma(a[i]*x[i]) - L * segma(b[i]*x[i]) = segma((a[i] - L * b[i]) * x[i])。令c[i] = a[i] - L * b[i]。
观察F(L),发现以下性质:1、若L固定,则可以通过一次对c[i]的排序,找到使得F(L)取最值的数组x[n];2、若x[n]固定,则F(L)随L的增大而减小;3、在2的基础上发现,即使x[n]不固定,F(L)也随着L的增大而减小;4、对于L0,若有F(L0) > 0,则由代数变形可知segma(a[i]*x[i]) / segma(b[i]*x[i]) > L0,所以R > L0。同理,若F(L0) < 0,则R < L0,若F(L0) = 0,则R = L0。
则由性质3,4可知,该问题能用二分法得到解决。
-------------------------------------------------------------二分法-------------------------------------------------------
设置下界l和上届r,mid = (l+r)/2。另c[i] = a[i] - mid*b[i],选出最大的(n-m)个c[i]之和,若和大于等于0,则l = mid,否则r = mid,直到满足精度为止。
-----------------------------------------------------Dinkelbach算法分析------------------------------------------------
但是,二分法解决此问题速度比较慢,还有一种难懂但是速度更快的算法Dinkelbach。
对F(L),设F(R) = 0。首先,随机选择一个数k1有两种情况:1、若F(k1) > 0,则k1 < R,记使得F(k1)取最大值的数组x[n]为x1[n]。令k2 = segma(a[i]*x1[i]) / segma(b[i]*x1[i]),由题设易知,k2 <= R,而F(k1) > 0 = F(k2),故k2 > k1,所以有k1 < k2 <= R,故令k1 = k2可更接近R,且下一次仍然有F(k1) > 0;2、F(k1) < 0,则k1 > R,记使得F(k1)取得最大值的数组x[n]为x2[n]。令k2 = segma(a[i]*x2[i]) / segma(b[i]*x2[i]),由题设知k2 <= R,则令k1 = k2,从下依次开始,一直有F(k1) > 0,由第一种情况组成循环。故可以不断通过k1 = k2的方式使得k1 更接近R。
-------------------------------------------------------Dinkelbach算法--------------------------------------------------
随机取一个数k,令c[i] = a[i] - k * b[i]并对c[i]进行排序,选出最大的m个,令k' = segma(a'[]) / segma(b'[]),(其中a'[]和b'[]事排序后选出来的最大的m个),若k'与k的差距在精度局限内则输出,否则k = k',循环上诉步骤。
参考资料:http://www.cnblogs.com/perseawe/archive/2012/05/03/01fsgh.html
http://blog.sina.com.cn/s/blog_6383bcba0100xf4z.html
http://hi.baidu.com/misshanli/item/ad81b23110f7ccb3134b14c3
例题:
1、POJ 2976 Dropping tests
题意:给定一个正整数m和两个正数数组a[n]和b[n]。对于数组x[n],x[n]中有m个i使得x[i] = 1,其余x[i] = 0,并且令R = segma(a[i]*x[i]) / segma(b[i]*x[i])。求一个满足条件的x[n],使得R的值最大,输出R的最大值。(注意看,与上面的题目有所不同)
1 <= n <= 1000,0 <= m < n,0 <= ai <= bi <= 10^9。
解法:不多说,完全就是上面那样。直接看代码。
tag:math, 01分数规划
二分法:
1 /* 2 * Author: Plumrain 3 * Created Time: 2013-10-13 10:18 4 * File Name: math-POJ-2976.cpp 5 * 二分法 6 */ 7 #include<iostream> 8 #include<cstdio> 9 #include<cstring> 10 #include<cmath> 11 #include<algorithm> 12 13 using namespace std; 14 15 #define CLR(x) memset(x, 0.0, sizeof(x)) 16 17 const double eps = 1e-4; 18 const int N = 1000; 19 20 int n, m; 21 int a[N+5], b[N+5]; 22 double c[N+5]; 23 24 bool cmp(double x, double y) 25 { 26 return x > y; 27 } 28 29 double gao() 30 { 31 double l = 0.0, r = 1.0; 32 while (l + eps < r){ 33 double mid = (l+r) / 2.0; 34 CLR (c); 35 for (int i = 0; i < n; ++ i) 36 c[i] = a[i] - mid * b[i]; 37 sort(c, c+n, cmp); 38 double tmp = 0.0; 39 for (int i = 0; i < n-m; ++ i) 40 tmp += c[i]; 41 if (tmp >= eps) l = mid; 42 else r = mid; 43 } 44 return l; 45 } 46 47 int main() 48 { 49 while (scanf ("%d%d", &n, &m) != EOF && n){ 50 for (int i = 0; i < n; ++ i) 51 scanf ("%d", &a[i]); 52 for (int i = 0; i < n; ++ i) 53 scanf ("%d", &b[i]); 54 55 printf ("%d\n", (int)(gao()*100.0 + 0.5)); 56 } 57 return 0; 58 }
Dinkelbach算法:
1 /* 2 * Author: Plumrain 3 * Created Time: 2013-10-13 17:29 4 * File Name: math-POJ-2976-2.cpp 5 * Dinkelbach算法 6 */ 7 #include<iostream> 8 #include<cstdio> 9 #include<cstring> 10 #include<cmath> 11 #include<algorithm> 12 #include<vector> 13 #include<utility> 14 15 using namespace std; 16 17 #define CLR(x) memset(x, 0.0, sizeof(x)) 18 #define PB push_back 19 20 const double eps = 1e-4; 21 const int N = 1000; 22 23 int n, m; 24 int a[N+5], b[N+5]; 25 vector<pair<double, int> > cur; 26 27 bool cmp(pair<double, int> x, pair<double, int> y) 28 { 29 return x.first > y.first; 30 } 31 32 double gao() 33 { 34 double p = 0.5, q = 0.0; 35 while (1){ 36 cur.clear(); 37 for (int i = 0; i < n; ++ i){ 38 double c = a[i] - p * b[i]; 39 cur.PB (make_pair(c, i)); 40 } 41 sort (cur.begin(), cur.end(), cmp); 42 double tmp1 = 0.0, tmp2 = 0.0; 43 for (int i = 0; i < n-m; ++ i){ 44 tmp1 += a[cur[i].second]; 45 tmp2 += b[cur[i].second]; 46 } 47 q = tmp1 / tmp2; 48 if (fabs(p-q) < eps) break; 49 p = q; 50 } 51 return q; 52 } 53 54 int main() 55 { 56 while (scanf ("%d%d", &n, &m) != EOF && n){ 57 for (int i = 0; i < n; ++ i) 58 scanf ("%d", &a[i]); 59 for (int i = 0; i < n; ++ i) 60 scanf ("%d", &b[i]); 61 62 printf ("%d\n", (int)(gao()*100.0+0.5)); 63 } 64 return 0; 65 }
现在的你,在干什么呢?
你是不是还记得,你说你想成为岩哥那样的人。