版权声明:本文为博主原创文章,未经博主允许不得转载,除非先点了赞。 https://blog.csdn.net/A_Bright_CH/article/details/83045130
题意
每条边都有两个值ci、ri,选择一棵生成树,使这棵树上的最小。
题解
0/1分数规划+最小生成树判断
老套路,把公式转变成
,
因为其符合生成树的定义,所以对其二分需要以生成树的形式。即以为边权,做一次最小生成树,如果存在大于等于0的答案,说明mid大了,还可以更小,r=mid;否则l=mid。
总结
和 洛谷2868 [USACO07DEC]观光奶牛Sightseeing Cows 一起总结吧。
0/1分数规划一定是通过二分来确定最优值的,如何判断才是一个重难点。一般来说,它的原型是什么就用什么来判断,在图论中,把边权、点权改成 xx - yy * mid 的形式来check就好了。
代码
注释掉的部分本来想写堆优化的prim,结果写炸了....
#include<cmath>
#include<queue>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef pair<double,int> pdi;
const double eps=1e-5;//debug int
const int maxn=1010;
int n;
int X[maxn],Y[maxn],H[maxn];
double e[maxn*maxn];
double d[maxn];bool vis[maxn];
priority_queue<pdi> q;
double calc(int i,int j,double mid)
{
double r=sqrt((double)(X[i]-X[j])*(X[i]-X[j])+(double)(Y[i]-Y[j])*(Y[i]-Y[j]));
double c=abs(H[i]-H[j]);
return c-mid*r;
}
double a[maxn][maxn];
double prim(double mid)//最小生成树
{
for(int i=1;i<=n;i++)
for(int j=i;j<=n;j++) a[i][j]=a[j][i]=calc(i,j,mid);
double re=0;
memset(vis,false,sizeof(vis));vis[1]=true;
d[0]=1<<30;d[1]=0;for(int i=2;i<=n;i++) d[i]=a[1][i];
/*d[1]=0;for(int i=2;i<=n;i++) d[i]=1<<30;
q.push(make_pair(0,1));
while(!q.empty())*/
for(int i=2;i<=n;i++)
{
/*int x=q.top().second;q.pop();
if(vis[x]) continue;*/
int x=0;
for(int j=1;j<=n;j++)
if(!vis[j] && d[j]<d[x]) x=j;//debug j->i
vis[x]=true;
re+=d[x];
for(int y=1;y<=n;y++)
if(!vis[y] && a[x][y]<d[y]) d[y]=a[x][y];
/*for(int y=1;y<=n;y++)
{
if(vis[y]) continue;
double cc=calc(x,y,mid);
if(cc<d[y])
{
d[y]=cc;
q.push(make_pair(cc,y));
}
}*/
}
return re;
}
bool check(double mid)//判断比率能否小于mid
{
return prim(mid)<=eps;//debug >=
}
int main()
{
while(scanf("%d",&n),n!=0)
{
for(int i=1;i<=n;i++)
{
scanf("%d%d%d",&X[i],&Y[i],&H[i]);
}
double l=0,r=1e5,ans;
while(l-r<=eps)//while(l<r)
{
double mid=(l+r)/2;
if(check(mid))
{
ans=mid;
r=mid-eps;
}
else l=mid+eps;
}
printf("%.3f\n",ans);
}
return 0;
}