2019 UESTC ACM Training for Graph[O] (最优比例生成树)

知识共享许可协议 Creative Commons

题意:空间中内有n个点,连n-1条边,使得这些边 的高度差之和比上水平距离之和最小。

最优比例生成树问题
对于一个最小生成树,可以表达为如下形式:
x1 * a1+x2 * a2+…+xm * am
其中x1 ~ xm要么为1要么为0,并且xi的和为n-1。ai是编号为i的边的权值,并且选择的点要使得图联通。
那么对于题中的问题,取一个生成树满足sum{xi * hi}/sum{xi * di}>=ans
对上式变形,得到:
sum{xi * (hi-ans * di)}>=0
很显然,这就是普通的最小生成树的形式,每条边的边权为hi-ans * di,并且由于ans有单调性,二分ans然后求最小生成树,然后判断最小生成树的和是否大于零,然后卡左右边界的值即可。
但是二分的方法会超时。因此考虑使用迭代的方法解决。
假设当前讨论到的答案是x,那么每条边的边权为hi-x * di。用变量x0记录之前算出的答案x,然后跑最小生成树,记录sumh,sumd为这次最小生成树中高度差的和以及水平距离的和,然后用sumh/sumd当作新的x值进行迭代计算。直到x和x0相比不再变小,就可以停止迭代了。
这种算法的正确性建立在x的每次迭代和上次相比会单调递减。更具体的分析参考博客:https://blog.csdn.net/chenzhenyu123456/article/details/48160209

#include<iostream>
#include<cstring>
#include<cstdlib>
#include<algorithm>
#include<queue>
#include<stack>
#include<vector>
#include<cmath>
#include<map>
#define LL long long
using namespace std;
const int maxn=1005;
const double inf=1e10;
const double eps=1e-4;
inline void _read(int &x){
    char t=getchar();bool sign=true;
    while(t<'0'||t>'9')
    {if(t=='-')sign=false;t=getchar();}
    for(x=0;t>='0'&&t<='9';t=getchar())x=x*10+t-'0';
    if(!sign)x=-x;
}
int n,fa[maxn];
double x[maxn],y[maxn],z[maxn];
int getfa(int a){return a==fa[a]?a:fa[a]=getfa(fa[a]);}
struct Edge{
	int from,to;
	double d,h,dis;
	Edge(int from,int to,double d,double h,double dis):from(from),to(to),d(d),h(h),dis(dis){}
	bool operator <(const Edge& red)const{
		return dis < red.dis;
	}
};
vector<Edge>edges;
void addedges(int from,int to,double d,double h){
	edges.push_back(Edge(from,to,d,h,0));
}
double get_dist(int i,int j){
	return sqrt((x[i]-x[j])*(x[i]-x[j])+(y[i]-y[j])*(y[i]-y[j]));
}
int main(){
	_read(n);
	for(int i=1;i<=n;i++)cin>>x[i]>>y[i]>>z[i];
	for(int i=1;i<n;i++)
		for(int j=i+1;j<=n;j++)
			addedges(i,j,get_dist(i,j),fabs(z[i]-z[j]));
	double cur=0,ans=inf;
	while(1){
		for(int i=1;i<=n;i++)fa[i]=i;
		for(int i=0;i<edges.size();i++)edges[i].dis=edges[i].h-edges[i].d*cur;
		sort(edges.begin(),edges.end());
		double sum1=0,sum2=0,sum=0;
		for(int i=0;i<edges.size();i++){
			int u=edges[i].from,v=edges[i].to;
			int fu=getfa(u),fv=getfa(v);
			if(fu!=fv)fa[fu]=fv,sum+=edges[i].dis,sum1+=edges[i].h,sum2+=edges[i].d;
		}
		cur=sum1/sum2;
		if(fabs(cur-ans)<=eps)break;
		ans=cur;
	}
	printf("%.3lf",ans);
}

猜你喜欢

转载自blog.csdn.net/getsum/article/details/91846316
今日推荐