标签:down 一条直线 node day push operator number xmlns 怎么
题目描述:
Input
Output
Sample Input
4 0 0 0 0 1 1 1 1 2 1 0 3 0
Sample Output
1.000
题意:有一张无向图,每条边有费用值和长度值,要求找到一颗生成树,使得总费用/总长度最小
问题模型:最优比率生成树
解题方法:
设r=Σc[i]*x[i]/(Σl[i]*x[i])(x[i]=0/1)
目标,最小化r
变形得:Σc[i]*x[i]-r*Σl[i]*x[i]=0
设f[r]=Σc[i]*x[i]-r*Σl[i]*x[i]=0
于是对于每一组x[i],以我们得到一条直线,r是这条直线的横截距
我们的目标变成,找到全部直线中的最小横截距
怎么找呢?首先暴力枚举x[i]绝对是不可取的。然后,然后我们发现其实只需要二分r的值,然后观察max(f[r])就好,如果min(f[r])还要小于0的话,我们就可以知道r>r*(r*是我们最后的所求值),反之r<r*。
当我们我们发现min(f[r])==0时,我们认为r=r*然后就直接输出就好了
考虑一下二分的时间复杂度,完全没有问题
(图片来自大佬ztx的CSDN博客,https://blog.csdn.net/hzoi_ztx/article/details/54898323,感谢大佬)
然而,我们还有一种方法(DinkelbachDinkelbach算法)
基本思想,其实和二分有点像,但它是基于迭代的。我们考虑如上述二分,我们有一个初始值r,然后我们发现min(f[r])还要小于0,那我们就直接把r转移到min(f[r])所代表的直线的横截距上。读者细细想想
就知道,不可能得到一个比r*还要小的值,因为r*就是最小的截距了。于是我们只需要不断迭代转移r就好了。
咳咳,差点忘了。怎么求min(f[r]),好吧其实很显然,为了得到min(f[r])的x[i],我们得把边的长度改一改跑最小生成树
代码里是堆优化的prim(我懒。。。不手写堆的)
下面附上我的DinkelbachDinkelbach代码
#include<cstdio> #include<iostream> #include<algorithm> #include<memory.h> #include<queue> #include<math.h> using namespace std; const int maxn=1000+15; int n,tot; int head[maxn],in[maxn]; struct VILLAGE { double x;double y;double z; }v[maxn]; struct EDGE { int from;int to;int next;double len;double cost;double e; }edge[maxn<<12]; struct NODE{ int x;double l;double c;double d; }; bool operator < (const NODE a,const NODE b){ return a.l>b.l; } void init(){ memset(edge,0,sizeof(edge)); memset(head,0,sizeof(head)); tot=0; } void add(int x,int y,double len,double cost){ edge[++tot]=(EDGE){x,y,head[x],len,cost,0}; head[x]=tot; } double prim(double oo) { memset(in,0,sizeof(in)); for (int i=1;i<=tot;i++) { edge[i].e=edge[i].cost-oo*edge[i].len; //printf("%d %d %lf\n",edge[i].from,edge[i].to,edge[i].e); } priority_queue<NODE> q; q.push((NODE){1,0,0,0}); int cnt=0; double c1=0,d1=0; while (!q.empty()&&cnt<n) { NODE k=q.top();q.pop(); if (in[k.x]) continue; in[k.x]=1; c1+=k.c;d1+=k.d;cnt++; for (int i=head[k.x];i;i=edge[i].next) { int y=edge[i].to; q.push((NODE){y,edge[i].e,edge[i].cost,edge[i].len}); } } //printf("\n%lf\n",c1/d1); return c1/d1; } int main() { while (1) { scanf("%d",&n); if (n==0) break; init(); for (int i=1;i<=n;i++) scanf("%lf%lf%lf",&v[i].x,&v[i].y,&v[i].z); for (int i=1;i<n;i++) for (int j=i+1;j<=n;j++) { double dis=sqrt((v[i].x-v[j].x)*(v[i].x-v[j].x)+(v[i].y-v[j].y)*(v[i].y-v[j].y)); double cost=fabs(v[i].z-v[j].z); add(i,j,dis,cost); add(j,i,dis,cost); } double r1=0.0,r2=0.0; while (1){ r2=prim(r1); if (fabs(r2-r1)<0.00001) break; r1=r2; } printf("%.3f\n",r1); } return 0; }
解决了最小的问题,最大的问题不也就迎刃而解了,只需要找最大截距就好了。这是0/1分数规划的典型之一。
其他的还有最优比率环什么的。
标签:down 一条直线 node day push operator number xmlns 怎么
原文地址:https://www.cnblogs.com/xxzh/p/9158455.html