BSOJ2923 CODEVS1419 藤原妹红 最小生成树+树形dp

2923 -- 【模拟试题】藤原妹红
Description
  在幻想乡,藤原妹红是拥有不老不死能力的人类。虽然不喜欢与人们交流,妹红仍然保护着误入迷途竹林村民。由于妹红算得上是幻想乡最强的人类,对于她而言,迷途竹林的单向道路亦可以逆行。在妹红眼中,迷途竹林可以视为一个由N个路口(编号1..N),M条不同长度双向路连接的区域。妹红所在的红之自警队为了方便在迷途竹林中行动,绘制了一张特殊的迷途竹林地图,这张地图上只保留了N-1条道路,这些道路保证了任意两个路口间有且仅有一条路径,并且满足所有保留的道路长度之和最小,我们称这些道路为『自警队道路』。现在妹红打算在其中一个连接有多条『自警队道路』的路口设立根据地,当去掉这个根据地所在路口后,就会出现某些路口间无法通过『自警队道路』相互连通的情况,我们认为这时仍然能够通过『自警队道路』连通的路口属于同一个『区域』。妹红希望最后每个『区域』的『自警队道路』总长尽可能平均,请计算出她应该选择哪一个路口作为根据地。
  下例中红色的路口为妹红选择的根据地,实线边表示『自警队道路』,绿色虚线边表示非『自警队道路』,数字表示边权,『自警队道路』中相同颜色的实线边代表属于同一个『区域』:


  (尽可能平均即权值最小,设每一块『区域』的路线总长为 Length[i],平均路线长度为Avg=SUM{Length[i]}/区域数,权值d=∑((Length[i]-Avg)^2))
Input
  第1行:2个正整数N,M。
  第2..M+1行:每行2个整数u,v和1个实数len,表示u,v之间存在长度为len的边。
Output
  第1行:1个整数,最后选择的路口编号,存在多个可选路口时选择编号小的。
Sample Input
3 3
3 1 5
3 2 4
1 2 3
Sample Output
2
Hint
【样例解释】
  妹红的『固定道路』为(1,2)和(2,3)。只能选择2作为根据地,产生的两个区域Length[i]分别为3和4,所以方差为:(4-3.5)^2+(3-3.5)^2=0.5
【数据范围】
  对于60%的数据:3≤N≤2,000,N-1≤M≤50,000
  对于100%的数据:3≤N≤40,000,N-1≤M≤200,000
    对于100%的数据:0 < len ≤ 100,000,000
【注意】
  保证不存在相同距离的线路,两个路口间可能出现多条路径,且任意点对间至少存在一条路径。


题解:
     树形dp的模板,可以参考树的重心。
     首先用Kruskal求出最小生成树,再树形dp就可以了
     最后有大神题解。
#include<iostream>
#include<algorithm>
#include<cmath>
#include<cstring>
#include<cstdio>
#define eps 0.0000001
using namespace std;
struct nodee{int fr,to;double v;
}a[200005];
struct node{int to,nxt;double val;
}w[200005];
double sum=0,e[200005],ans=1e80,cur=0,val[200005];
int cnt=0,h[200005],fa[200005],n,m,c1=0,son[200005];
void add(int x,int y,int z)
{
	cnt++;w[cnt].to=y;w[cnt].nxt=h[x];w[cnt].val=z;h[x]=cnt;
}
bool cmp(nodee a,nodee b)
{
	return a.v-b.v<eps;
}
int get(int x)
{
	if(fa[x]==x)return x;
	return fa[x]=get(fa[x]);
}
void K()
{
	for(int i=1;i<=n;i++)fa[i]=i;
	sort(a+1,a+m+1,cmp);
	for(int i=1;i<=m;i++)
	{
		int f1=get(a[i].fr),f2=get(a[i].to);
		if(f1!=f2)
		{
			fa[f1]=f2,c1++;
			add(a[i].fr,a[i].to,a[i].v);
			add(a[i].to,a[i].fr,a[i].v);
			son[a[i].fr]++,son[a[i].to]++;
			sum+=a[i].v;
		}
		else continue;
		if(c1==n-1)break;
	}
}
void dp2(int pos,int fa)
{
	e[pos]=0;
	double avg=sum/(1.0*son[pos]);
	double mine=0;
	for(int i=h[pos];i;i=w[i].nxt)
	{
		int j=w[i].to;
		if(j==fa)continue;
		dp2(j,pos);
		val[pos]+=(w[i].val+e[j]-avg)*(w[i].val+e[j]-avg);
		e[pos]+=(w[i].val+e[j]);
		mine+=(w[i].val+e[j]);
	}
	if(abs(sum-mine)>eps)
	  val[pos]+=(sum-mine-avg)*(sum-mine-avg);
	if(son[pos]!=1&&(val[pos]-ans<eps||(val[pos]==ans&&pos<cur)))
	{
		ans=val[pos];
		cur=pos;
	}
	return ;
}
int main(){
	cin>>n>>m;
	for(int i=1;i<=m;i++)
	  scanf("%d%d%lf",&a[i].fr,&a[i].to,&a[i].v);
	K();
	dp2(1,0);
	cout<<cur;
	return 0;
}


     附上某大神题解和代码。
    
【分析】最小生成树 树的遍历

题目大意:给定一个无向有权图,首先一个最小生成树 MST,从 MST 中选取一个度数大于 1 的点 作为根 K,使每颗子树及该子树到根的边权之和方差最小。输出 K 和最小方差的值。

算法1

首先毫无疑问的需要用到求最小生成树的算法,我们考虑使用 Kruskal 算法或是Prim 算法。求出最小生成树以后,依次枚举每一个点作为根进行遍历,取出其中的最小方差即可。

时间复杂度:O(MlogM+N^2)

期望得分:60

算法2

由于后 40%的数据 N 比较大,所以只能通过 Kruskal 算法求出最小生成树,接下来任选一个点作为根,进行一次遍历。记录 w[i]表示以 i 点作为根的子树的边权之和。 然后依次枚举每一个点 i,该点的子树权值可以直接求出,而以它父亲作为根的子树需要特殊处理。这颗特殊子树的权值为最小生成树总权值减去该点权值 w[i]。然后计算出方差,最后选取所有点当中最小方差的那个点即可。

时间复杂度:O(MlogM+N)

期望得分:100
#include <iostream>
#include <vector>
#include <stdio.h>
#include <stdlib.h>
#include <algorithm>
using namespace std;

#define MAXN 50009
#define MAXE 200009
#define pb(x) push_back(x)
#define mk(x, y) make_pair(x, y)

struct EDGE
{
	int u, v;
	double length;
}	edge[ MAXE ];
int N, edgecnt;

struct Tree
{
	vector< pair<int, double> > fir[ MAXN ];
	double Sum, w[ MAXN ];
	int path[ MAXN ];
	bool vis[ MAXN ];

	void Addedge(int u, int v, double length)
	{
		Sum += length;
		fir[u].pb( mk(v, length) );
		fir[v].pb( mk(u, length) );
		return ;
	}

	void DFS(int now)
	{
		vis[ now ] = true;
		for (int i = 0; i != fir[now].size(); i++)
			if (!vis[ fir[now][i].first ])
			{
				path[ fir[now][i].first ] = now;
				DFS( fir[now][i].first );
				w[now] += fir[now][i].second + w[ fir[now][i].first ];
			}
		return ;
	}

	void GetAns()
	{
		int best(-1);
		double best_ans(0), tp(0), Avg(0);
		DFS(1);
		for (int i = 1; i <= N; i++)
            if (fir[i].size() > 1)
            {
                tp = 0;
                Avg = Sum / fir[i].size();
                for (int j = 0; j != fir[i].size(); j++)
                    if (fir[i][j].first != path[i])
                        tp += (Avg - fir[i][j].second - w[ fir[i][j].first ]) * (Avg - fir[i][j].second - w[ fir[i][j].first ]);
                    else tp += (Avg - (Sum - w[i])) * (Avg - (Sum - w[i]));
                if (best == -1 || tp < best_ans)
                    best_ans = tp, best = i;
            }
		printf("%d\n", best);
		return ;
	}
}	MST;

struct Kruskal
{
	int path[ MAXN ];

	int Find(int x)
	{
		if (x != path[x]) path[x] = Find( path[x] );
		return path[x];
	}

	void Input()
	{
		scanf("%d %d", &N, &edgecnt);
		for (int i = 1; i <= edgecnt; i++)
			scanf("%d %d %lf", &edge[i].u, &edge[i].v, &edge[i].length);
		return ;
	}

	void Work()
	{
		int cnt(1), x, y;
		for (int i = 1; i <= N; i++)
			path[i] = i;
		for (int i = 1; i <= edgecnt && cnt < N; i++)
		{
			x = Find( edge[i].u );
			y = Find( edge[i].v );
			if (path[x] == path[y]) continue;
			path[x] = path[y];
			MST.Addedge(edge[i].u, edge[i].v, edge[i].length);
			++cnt;
		}
		return ;
	}
}	Kruskal;

bool Comp(EDGE x, EDGE y)
{
    return x.length < y.length;
}

int main()
{

	Kruskal.Input();
	sort(edge + 1, edge + edgecnt + 1, Comp);
    Kruskal.Work();
	MST.GetAns();

	return 0;
}



     
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值