浅谈01分数规划

首先引出0/1分数规划的概念

给出一列数$a_i$与$b_i$,构造出一列数$x_i$,使得$frac{sum^{n}_{i=1}{a_i*{x_i}}}{sum^{n}_{i=1}{b_i*{x_i}}}$最大,求出这个最大值(满足$x_iinlbrace{0,1} brace$)

想要求出这个最大值一般使用二分答案

即当当前枚举的答案是$mid$时

判断是否有$frac{sum^{n}_{i=1}{a_i*{x_i}}}{sum^{n}_{i=1}{b_i*{x_i}}}geq mid$

如果有则$l=mid$,否则$r=mid$

那么如何快速求出不等式的左边呢?我们将原不等式变形

$$ sum^n_{i=1}{a_i*x_i}geq {mid*sum^n_{i=1}{b_i*x_i}} $$

$$ sum^n_{i=1}{a_i*x_i}-{mid*sum^n_{i=1}{b_i*x_i}}geq 0 $$

不等式左边将$x_i$提出去

$$x_i*(sum_{i=1}^n{a_i-mid*b_i})geq 0$$

因此我们可以在$O(n)$的时间内将$a_i-mid*b_i$处理出来,然后做一个简单的贪心

如果这个值$geq 0$,那么令这个$x_i=1$,否则令$x_i=0$

关于总的时间复杂度,二分答案$O(logn)$,检验答案合法性$O(n)$,总时间复杂度为$O(nlogn)$

一道例题

题目链接poj2976

看到式子就知道这大概是个模板题了

唯一的不同是这里只要求选出$n-k+1$个数

那么只要将所有的$sum_{i=1}^n{a_i-mid*b_i}$排序,直接选取前$n-k+1$个数相加即可

由于保证$a_i<b_i$,所以二分边界只要是$[0,1]$即可

 1 #include<iostream>
 2 #include<string>
 3 #include<string.h>
 4 #include<stdio.h>
 5 #include<algorithm>
 6 #include<vector>
 7 #include<queue>
 8 #include<map>
 9 using namespace std;
10 int a[1010],b[1010],n,k;
11 double sum[1010];
12 
13 bool check(double num)
14 {
15     int i;
16     for (i=1;i<=n;i++) sum[i]=(double)a[i]-num*b[i];
17     sort(sum+1,sum+1+n);
18     double s=0.0;
19     for (i=k+1;i<=n;i++) s+=sum[i];
20     if (s>=0) return 1;else return 0;
21 }
22 
23 int main()
24 {
25     scanf("%d%d",&n,&k);
26     while ((n!=0) || (k!=0))
27     {
28         int i;
29         for (i=1;i<=n;i++) scanf("%d",&a[i]);
30         for (i=1;i<=n;i++) scanf("%d",&b[i]);
31         double l=0.0,r=1.0;
32         while (r-l>1e-4)
33         {
34             double mid=(l+r)/2;
35             if (check(mid)) l=mid; else r=mid;
36         }
37         printf("%.0lf
",l*100);
38         scanf("%d%d",&n,&k);
39     }
40     return 0;
41 }

再来看另一道例题[USACO07DEC]Sightseeing Cows

我们用$fun[i]$表示在这个点可以获得的快乐值,$time[i]$表示在每条道路上花的时间

由于要求回答出发点,所以就是要找一个环

假设环上有$p$个点

那么我们要求的是$frac{sum^p_{i=1}{fun[i]}}{sum^p_{i=1}{time[i]}}=ans$中的$ans$最大

同样考虑二分答案

考虑$frac{sum^p_{i=1}{fun[i]}}{sum^p_{i=1}{time[i]}}> ans$

原式可改写为$sum^p_{i=1}(fun[i]-ans*time[i])>0$

在图上跑这个东西并不好跑

我们改变一下不等式的方向,即$sum^p_{i=1}(ans*time[i]-fun[i])<0$

于是我们可以把每条边的权值看做$ans*time[i]-fun[i]$

然后判负环就可以啦

方法:使用$spfa$,记录下每个点进队的次数,如果多于$n$那么就出现了负环

还要注意的是:图不一定是连通图,所以起始时可以让每个点都进入队列

由于判的是负环所以直接$dis[i]=0$就可以啦

话说在USACO里写spfa不会死吗(逃

 1 #include<iostream>
 2 #include<string>
 3 #include<string.h>
 4 #include<stdio.h>
 5 #include<algorithm>
 6 #include<vector>
 7 #include<queue>
 8 #include<map>
 9 using namespace std;
10 struct node{
11     int to,nxt,cost;
12 }sq[10010];
13 int n,m,fun[1010],head[1010],all=0,num[1010];
14 double dis[1010];
15 bool vis[1010];
16 
17 void add(int u,int v,int w)
18 {
19     all++;sq[all].to=v;sq[all].nxt=head[u];sq[all].cost=w;head[u]=all;
20 }
21 
22 bool check(double ans)
23 {
24     int i;
25     queue<int> q;
26     for (i=1;i<=n;i++)
27     {
28         q.push(i);
29         vis[i]=1;num[i]=1;
30         dis[i]=0.0;
31     }
32     while (!q.empty())
33     {
34         int i,u=q.front();q.pop();
35         vis[u]=0;
36         for (i=head[u];i;i=sq[i].nxt)
37         {
38             int v=sq[i].to;
39             if (dis[v]>(double)sq[i].cost*ans-fun[u]+dis[u])
40             {
41                 dis[v]=(double)sq[i].cost*ans-fun[u]+dis[u];
42                 if (!vis[v])
43                 {
44                     num[v]++;
45                     if (num[v]>=n) return 1;
46                     vis[v]=1;q.push(v);
47                 }
48             }
49         }
50     }
51     return 0;
52 }
53 
54 int main()
55 {
56     scanf("%d%d",&n,&m);
57     int i;
58     for (i=1;i<=n;i++) scanf("%d",&fun[i]);
59     for (i=1;i<=m;i++)
60     {
61         int u,v,w;
62         scanf("%d%d%d",&u,&v,&w);
63         add(u,v,w);
64     }
65     double l=0.0,r=1001000.0;
66     while (r-l>1e-4)
67     {
68         double mid=(l+r)/2;
69         if (check(mid)) l=mid; else r=mid;
70     }
71     printf("%0.2lf",l);
72     return 0;
73 }







原文地址:https://www.cnblogs.com/encodetalker/p/9763065.html