| Time Limit: 3000MS | Memory Limit: 65536K | |
| Total Submissions: 21361 | Accepted: 5974 |
Description
Input
Output
Sample Input
4 0 0 0 0 1 1 1 1 2 1 0 3 0
Sample Output
1.000
Source
01分数规划,最优比例生成树。
用两种方法:
1.二分法
二分一个答案k,
如果有sigma(Hi)/sigma(Di)<k的,即sigma(Hi)-k*sigma(Di)<0,则k要缩小。
那么我们就求出以Hi-k*Di为边权的最小生成树,看这棵树的权值和是否<0即可。
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
#include <cstdlib>
#include <cmath>
#define eps 1e-6
using namespace std;
int n,m,v[1005];
double d[1005];
struct edge
{
double h,d;
}a[1005][1005];
struct village
{
double x,y,h;
}vi[1005];
double Getdis(int x,int y)
{
return sqrt((vi[x].x-vi[y].x)*(vi[x].x-vi[y].x)+(vi[x].y-vi[y].y)*(vi[x].y-vi[y].y));
}
double prim(double k)
{
for (int i=1;i<=n;i++)
v[i]=0,d[i]=100000000.0;
d[1]=0.0;
double ans=0.0;
for (int i=1;i<=n;i++)
{
double minn=100000000.0;
int x=-1;
for (int j=1;j<=n;j++)
if (!v[j]&&d[j]<minn)
{
x=j;
minn=d[j];
}
v[x]=1;
ans+=d[x];
for (int j=1;j<=n;j++)
if (!v[j]&&d[j]>a[x][j].h-k*a[x][j].d)
d[j]=a[x][j].h-k*a[x][j].d;
}
return ans;
}
void Solve()
{
double l=0.0,r=1000.0;
while (r-l>eps)
{
double m=(l+r)/(double)2;
if (prim(m)<0) r=m;
else l=m;
}
printf("%.3f\n",l);
}
double abss(double x)
{
if (x>0) return x;
return -x;
}
int main()
{
while (scanf("%d",&n)!=EOF&&n)
{
for (int i=1;i<=n;i++)
scanf("%lf%lf%lf",&vi[i].x,&vi[i].y,&vi[i].h);
for (int i=1;i<=n;i++)
for (int j=i+1;j<=n;j++)
{
a[i][j].h=a[j][i].h=abss(vi[i].h-vi[j].h);
a[i][j].d=a[j][i].d=Getdis(i,j);
}
Solve();
}
return 0;
}
一开始为什么一直WA呢?
我对sigma(Hi)/sigma(Di)<k这个式子的变法是k*sigma(Di)-sigma(Hi)>0,因为这里是>0所以要求最大生成树!
2.Dinkelbach
这个方法是先随便给出一个答案l,然后根据这个l求最小生成树得到一个比率是k,这个k既然要比l更优,那么我们直接
用k迭代下去就可以,就不需要漫无目的的二分下去了。可以证明k最终会收敛到一个值上,这个值就是答案。
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cstdio>
#include <cstdlib>
#include <cmath>
#define eps 1e-6
using namespace std;
int n,m,v[1005];
double d[1005];
double aa[1005],bb[1005];
struct edge
{
double h,d;
}a[1005][1005];
struct village
{
double x,y,h;
}vi[1005];
double p,q;
double Getdis(int x,int y)
{
return sqrt((vi[x].x-vi[y].x)*(vi[x].x-vi[y].x)+(vi[x].y-vi[y].y)*(vi[x].y-vi[y].y));
}
double prim(double k)
{
for (int i=1;i<=n;i++)
v[i]=0,d[i]=100000000.0;
d[1]=0.0;
p=q=0.0;
for (int i=1;i<=n;i++)
{
double minn=100000000.0;
int x=-1;
for (int j=1;j<=n;j++)
if (!v[j]&&d[j]<minn)
{
x=j;
minn=d[j];
}
v[x]=1;
p+=aa[x],q+=bb[x];
for (int j=1;j<=n;j++)
if (!v[j]&&d[j]>a[x][j].h-k*a[x][j].d)
d[j]=a[x][j].h-k*a[x][j].d,aa[j]=a[x][j].h,bb[j]=a[x][j].d;
}
return p/q;
}
void Solve()
{
double l=10.0;
while (1)
{
double k=prim(l);
if (fabs(k-l)<eps) break;
l=k;
}
printf("%.3f\n",l);
}
double abss(double x)
{
if (x>0) return x;
return -x;
}
int main()
{
while (scanf("%d",&n)!=EOF&&n)
{
for (int i=1;i<=n;i++)
scanf("%lf%lf%lf",&vi[i].x,&vi[i].y,&vi[i].h);
for (int i=1;i<=n;i++)
for (int j=i+1;j<=n;j++)
{
a[i][j].h=a[j][i].h=abss(vi[i].h-vi[j].h);
a[i][j].d=a[j][i].d=Getdis(i,j);
}
Solve();
}
return 0;
}
这个方法要快很多。
但是有的题目是不适合用此法的,比如【POJ 3621】求负环这道题,很难去记录当前的比率是多少。
感悟:
1.在进行二分判断的时候,要注意如果是<0,就要找最小的看是否<0;如果是>0就要找最大的,看是否>0
2.对于Dinkelbach的证明,可以看这里。
我的大概理解是:通过当前的k值求得的新的比率要比k更优,我们一直取更优的,最终就会取到最优的了
原文地址:http://blog.csdn.net/regina8023/article/details/42676677