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;
}