浅谈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_i\in\lbrace{0,1}\rbrace$)
想要求出这个最大值一般使用二分答案
即当当前枚举的答案是$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\n",l*100); 38 scanf("%d%d",&n,&k); 39 } 40 return 0; 41 }
我们用$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 }