Given a tree, calculate the average distance between two vertices in the tree. For example, the average distance between two vertices in the following tree is (d 01 + d 02 + d 03 + d 04 + d 12 +d 13 +d 14 +d 23 +d 24 +d 34)/10 = (6+3+7+9+9+13+15+10+12+2)/10 = 8.6.
Input
On the first line an integer t (1 <= t <= 100): the number of test cases. Then for each test case:
One line with an integer n (2 <= n <= 10 000): the number of nodes in the tree. The nodes are numbered from 0 to n - 1.
n - 1 lines, each with three integers a (0 <= a < n), b (0 <= b < n) and d (1 <= d <= 1 000). There is an edge between the nodes with numbers a and b of length d. The resulting graph will be a tree.
Output
For each testcase:
One line with the average distance between two vertices. This value should have either an absolute or a relative error of at most 10 -6
Sample Input
1
5
0 1 6
0 2 3
0 3 7
3 4 2
Sample Output
8.6
题目链接
参考题解1
参考题解2
- 题意:给你一棵树,将其中所有两点直接能形成的通路加和,然后除以通路数量求平均值。如图,可以理解一下。
- 题目思路:刚开始我根本就不知道树形dp是个什么,然后这个题目就想着用dfs遍历整个图然后每两个点求一下加和,这样暴力来做,可是一想就会TLE。所以就看了一下题解,是用树形dp,然后看了一下思路,其实和原本的dfs差不多,只不过是在其中做了一些比较巧妙的改动,使得程序比较精巧。
首先,我们要求所有的通路权值加和,暴力肯定是不可以的,那么就换种思维方式,我们在加和的过程中,其实有很多边是要重复走多次的,那么这种边到底要走多少次呢?从一个端点到另一个端点,到以另一个端点为根的所有子树的节点,另一侧也是一样的道理,所以两边统计树中的节点个数就可以了。如果一个端点的子树节点个数为n个,那么另一个端点的子树节点个数就是m = (N - n)(N为这棵树所有节点的个数),因为整棵树,被这条边分成了两部分,节点被分在俩边,那么这条路径要被通过的次数就是n * m次,每一条边都是这样算。
那么这样可以随便选取一个点作为根节点,dfs往下面遍历没遇到一条边的时候就找这个边下面两点的个数相乘加到父节点上,最后所有权值之和就都集合到了最开始的根节点上。(笔者语言表达能力有限,如果还是不明白,请参考两个题解)
#include <cstdio>
#include <cstring>
#include <vector>
using namespace std;
#define LL long long
const int maxn = 1e4 + 5;
int t, n;
LL sum[maxn], dp[maxn];
struct node
{
int to, val;
};
vector<node> vec[maxn];
void init()
{
memset(dp, 0, sizeof(dp));
for(int i = 0; i < n; i++)
{
sum[i] = 1; //默认自己这个节点算作一个,后面每有一个子树就加一
vec[i].clear();
}
}
void tree_dp(int root, int father) //root记录当前这个要寻找子树的节点,即以当前节点作为树根,father代表这个节点的来源,这个节点的父节点
{
int len = vec[root].size(); //相连边的条数
for(int i = 0; i < len; i++)
{
int son = vec[root][i].to;
if(son == father) //如果是父节点的话就跳过
continue ;
tree_dp(son, root); //继续找子树的子树
int val = vec[root][i].val;
//将子树的个数加到当前根节点上,这次调用完成之后返回,也会被当做子节点使用。所以要把这个以这个点为根节点的所有子树个数统计下来
sum[root] += sum[son];
//dp记录当前这条边以及之后的所有权值之和,最后都加在最开始调用时的根节点上,即为最后的总和
dp[root] = dp[root] + dp[son] + sum[son] * (n - sum[son]) * val;
}
}
int main()
{
scanf("%d", &t);
while(t--)
{
scanf("%d", &n);
init();
int to, from, val;
for(int i = 0; i < n - 1; i++)
{
scanf("%d%d%d", &from, &to, &val);
//建立双向边
//node{参数1, 参数2},这样可以直接传入,避免代码的麻烦
vec[from].push_back(node{to, val});
vec[to].push_back(node{from, val});
}
//结点从0开始,那么就默认以0为根节点进行遍历,根节点没有父节点,所以设根节点为-1
tree_dp(0, -1);
//计算通路的数量,n个节点,连个点就可以构成一条通路,c(n,2)
int temp = n * (n - 1) / 2;
printf("%f\n", dp[0] / (temp * 1.0));
}
return 0;
}