@happyforever
2018-08-02T00:39:38.000000Z
字数 4577
阅读 2125
二分答案 01分数规划
给定个二元组,其中表示的是选择此二元组获得的价值,是选择此二元组付出的代价,假设表示第i个二元组选不选,求下式的最大(小)值:

对于每一条直线,横截距就是这一组的,那么就是每条直线横截距的最大值(每组对应的最大值)

接下来考虑二分:在图上任取一条垂直于轴的竖线
如果存在某条直线与这条竖线的交点的纵坐标为正,那么最优值一定在当前竖线的右侧
如果所有直线与这条竖线的交点纵坐标为负,那么最优值一定在当前竖线的左侧
如果所有直线与这条竖线的交点纵坐标为负存在直线与这条竖线交点纵坐标为零,那么当前竖线横坐标即为最优值
选择一个值时需要判断是否大于0,若大于0那么
否则
思考上述的二分算法的思路,设二分过程中的一个二分值为x,二分时的判断条件是的正负性,而这个x除了让右移或者左移就没有其他作用了
现在思考某一过程中x与能否再被利用
二分时,假如这说明最优解在当前x的右侧,于是让L=x,但是,如果将L移动到对应直线的横截距呢?显然,算法会变得更快。这个思想就是Dinkelbach算法的内涵。
Dinkelbach实质上是一种迭代算法,基于这样的思想:不去二分答案,而是先随便给定一个答案,然后根据更优的解(对应直线的横截距)不断移动答案,逼近最优解。理论上它比二分快些。
在这个算法中,一般将x初始化为0。
不过弊端是需要保存解。
实际应用中这两个算法速度不相上下
不过还是二分更好理解一些
例题:Poj 2976
给出n个物品,每个物品有两个属性a和b,选择n-k个元素,询问的最大值。
裸题,不多说
用的二分
#include <cstdio>#include <algorithm>const int N=1001;const double eps=1e-6;int n,k,a[N],b[N];double ps[N];inline bool judge(double x){for(int i=1;i<=n;++i)ps[i]=a[i]-x*b[i];std::sort(ps+1,ps+1+n);double ans=0;for(int i=n;i>=k+1;--i)ans+=ps[i];return ans>=0;}int main(){while(scanf("%d%d",&n,&k),n|k){for(int i=1;i<=n;++i)scanf("%d",&a[i]);for(int i=1;i<=n;++i)scanf("%d",&b[i]);double l=0,r=1,mid;while(r-l>eps){mid=(l+r)/2;if(judge(mid))l=mid;else r=mid;}printf("%.0lf\n",l*100);}return 0;}
另外还有一种做法:既然可以二分一个值,而且得出一个更优的解,那么就可以在这个解的基础上继续做,直到满足精度
比上面的做法快一些
带权无向图G,对于图中每条边,都有和,现在求一棵生成树T,最大(小)化
解决方法:
套用01分数规划模型,如果则,否则
二分答案ans,边赋值,因为是生成树,边的数量确定,那么需要选取前大的,也就是求最大生成树,按最大生成树权值的正负性就可以二分了。最小化就求最小生成树
当前答案ans,边赋值,同样求最大生成树,找到对应的边集,也就是最大生成树的边集。对这个边集找横截距当做下一次答案。
横截距是
#include <cmath>#include <cstdio>#include <cstring>#include <algorithm>using namespace std;const double eqs=1e-6;const int INF=0x3f3f3f3f;struct point{double x,y,z;}p[1100];int vis[1100],n;double dis[1100];double f(int i,int j,double mid){return fabs(p[i].z-p[j].z)-mid*sqrt((p[i].x-p[j].x)*(p[i].x-p[j].x)+(p[i].y-p[j].y)*(p[i].y-p[j].y));}double solve(double mid){int id,u;double ans=0,min1;memset(vis,0,sizeof vis);for(int i=0;i<n;++i)dis[i]=INF;vis[0]=1;dis[0]=0;u=0;for(int i=1;i<n;++i){min1=INF;id=0;for(int j=0;j<n;++j){if(vis[j])continue;dis[j]=min(dis[j],f(u,j,mid));if(min1-dis[j]>=eqs){min1=dis[j];id=j ;}}ans+=min1;vis[id]=1;u=id;}return ans;}int main(){double temp,max1,min1,l,r,mid;while(scanf("%d",&n)&&n){l=r=0.0;max1=0;min1=INF;for(int i=0;i<n;++i){scanf("%lf %lf %lf",&p[i].x,&p[i].y,&p[i].z);min1=min(min1,p[i].z);max1=max(max1,p[i].z) ;}r=(max1-min1)*n;while(r-l>eqs){mid=(l+r)/2.0 ;temp=solve(mid);if(fabs(temp)<eqs)break;if(temp<0)r=mid;else l=mid;}printf("%.3f\n",mid) ;}return 0;}
给定有边权和点权的图,求一个环使得点权和与边权和的比值最大
解决方法:
套用01分数规划模型,点权为,边权为,一个环为
问题要求最大化
若答案为r*,那么对于任意一个环有
求最小化的时候

说明:为什么答案一定是简单环。
假设最优解是由两个环相交而来,设交点收益为a,两个环记作1,2。
有
化简得
则,矛盾。
故答案一定是简单环。
所以就是个最优比率环
#include<queue>#include<cmath>#include<cstdio>#include<cstring>const double eps=1e-3;int fir[1010],ne[5010],to[5010],cost[5010],val[1010],cnt[1010],m,n;double dis[1010];bool find(double x){std::queue<int> q;int p;for(int i=1;i<=n;++i)q.push(i);memset(cnt,0,sizeof(cnt));memset(dis,0,sizeof(dis));while(!q.empty()){p=q.front();q.pop();for(int i=fir[p];i;i=ne[i])if(x*cost[i]-val[to[i]]+dis[p]<dis[to[i]]){dis[to[i]]=x*cost[i]-val[to[i]]+dis[p];if(cnt[to[i]]==n-1)return 1;++cnt[to[i]];q.push(to[i]);}}return 0;}void solve(){double x,y,z,l,r,mid;l=0;r=1e6;while(r-l>=eps){mid=(l+r)/2;if(find(mid))l=mid;else r=mid;}if(l<eps)printf("0\n");else printf("%.2f\n",l);}int main(){scanf("%d%d",&n,&m);for(int i=1;i<=n;++i)scanf("%d",&val[i]);for(int x,i=1;i<=m;++i){scanf("%d%d%d",&x,&to[i],&cost[i]);ne[i]=fir[x];fir[x]=i;}solve();}