hdu5723

本文介绍了一道经典的算法题目,通过构建最小生成树解决村庄间道路重建的问题,并计算任意两点间距离的期望值。文章详细解析了解题思路,包括使用Kruskal算法寻找最小生成树,以及如何计算树上任意两点间距离的总和。

Abandoned country

Time Limit: 8000/4000 MS (Java/Others) Memory Limit: 65536/65536 K (Java/Others)
Total Submission(s): 5211 Accepted Submission(s): 1305

Problem Description
An abandoned country has n(n≤100000) villages which are numbered from 1 to n. Since abandoned for a long time, the roads need to be re-built. There are m(m≤1000000) roads to be re-built, the length of each road is wi(wi≤1000000). Guaranteed that any two wi are different. The roads made all the villages connected directly or indirectly before destroyed. Every road will cost the same value of its length to rebuild. The king wants to use the minimum cost to make all the villages connected with each other directly or indirectly. After the roads are re-built, the king asks a men as messenger. The king will select any two different points as starting point or the destination with the same probability. Now the king asks you to tell him the minimum cost and the minimum expectations length the messenger will walk.

Input
The first line contains an integer T(T≤10) which indicates the number of test cases.

For each test case, the first line contains two integers n,m indicate the number of villages and the number of roads to be re-built. Next m lines, each line have three number i,j,wi, the length of a road connecting the village i and the village j is wi.

Output
output the minimum cost and minimum Expectations with two decimal places. They separated by a space.

Sample Input
1
4 6
1 2 1
2 3 2
3 4 3
4 1 4
1 3 5
2 4 6

Sample Output
6 3.33

Author
HIT

Source
2016 Multi-University Training Contest 1
题意:有n个城市,m条需要被重建的路,每条路有权值且各不相同,让你使所有城市相通(直接或间接)的情况下,找出权值和最小的那种方案,然后问你从这种方案中任意选取两点,问这两点之间的距离的最小期望值是多少。
解题思路:让你找出权值和最小,当然是最小生成树啊,但是后面那个最小期望有点麻烦,不过我们可以仔细分析得到,每条路的权值都不一样所得出的最小生成树是唯一的(具体证明自己百度),那么生成树唯一,怎样在这棵树上求最小期望,最小期望?这其实是出题人忽悠你的,一棵树上任意两点之间的距离唯一,所以不存在最小最大之说,只要找出任意两两点之间的距离就行,然后把所有的距离加起来除以n*(n - 1)/2就行,那么问题就转化为在一无根树上求任意两点之间的距离之和,我们分析可以得到,所有距离之和等于每一条边的权值乘以这条边出现的次数之和,每一条边的权值我们知道,我们现在要知道每一条边出现过多少次,而我们可以发现,每条边出现的次数等于这条边的两个端点两侧`顶点数目的乘积,知道了这一点,我们就可以直接dfs了,任意选取一个顶点为根(我这里选的是1),令dp[u]等于以u为根的子树的顶点数,然后枚举每一条边,假如一条边的两个顶点是x1,x2,那么x1,x2中要么x1是x2的父亲,要么x2是x1的父亲,那么到底谁是谁的父亲,很简单,比较dp[x1],dp[x2],小的那个一定是孩子,所有这条边的一侧的顶点数为v1 = min(dp[x1],dp[x2]),那么另一侧顶点数是多少呢?很简单,总数是n ,所有另一侧顶点数v2 = n - v1,所有这条边出现的次数为v1v2,注意最后求期望除以n*(n - 1)/2时一定要将n,n - 1变成long long 之后再求,我在这里wa了无数次。

#include<bits/stdc++.h>
using namespace std;
const int maxn = 1e6 + 10;
typedef long long ll;
int n,m;
int cnt1;
int cnt2;
ll dp[maxn];
int father[maxn];
vector<int> g[maxn];
bool visit[maxn];
struct edge{
    int u;
    int v;
    ll w;
}Edge1[maxn];
edge Edge2[maxn];
int Find(int x)
{
    if(x == father[x]) return x;
    else return father[x] = Find(father[x]);
}
void add1(int u,int v,ll w)
{
    Edge1[cnt1].u = u;
    Edge1[cnt1].v = v;
    Edge1[cnt1++].w = w;
}
void add2(int u,int v,ll w)
{
    Edge2[cnt2].u = u;
    Edge2[cnt2].v = v;
    Edge2[cnt2++].w = w;
}
bool cmp(edge e1,edge e2)
{
    return e1.w < e2.w;
}
ll kruscal()
{
    sort(Edge1,Edge1 + cnt1,cmp);
    ll sum = 0;
    int ans = 0;
    for(int i = 0; i < cnt1; i++)
    {
        int u = Edge1[i].u;
        int v = Edge1[i].v;
        ll w = Edge1[i].w;
        int f1 = Find(u);
        int f2 = Find(v);
        if(f1 != f2)
        {
            father[f1] = f2;
            ans++;
            sum += w;
            g[u].push_back(v);
            g[v].push_back(u);
            add2(u,v,w);
        }
        if(ans == n - 1) break;
    }
    return sum;
}
void init()
{
    cnt1 = 0;
    cnt2 = 0;
    for(int i = 1; i <= n; i++)
    {
        father[i] = i;
        g[i].clear();
    }
}
void dfs(int x)
{
    visit[x] = true;
    dp[x] = 1;
    for(int i = 0; i < g[x].size(); i++)
    {
        int y = g[x][i];
        if(!visit[y])
        {
            dfs(y);
            dp[x] += dp[y];
        }
    }
}
int main()
{
    int T;
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d%d",&n,&m);
        init();
        if(m == 0)
        {
            printf("0 0.00\n");
            continue;
        }
        int from,to;
        ll w;
        for(int i = 1; i <= m; i++)
        {
            scanf("%d%d%I64d",&from,&to,&w);
            add1(from,to,w);
        }
        ll sum = kruscal();
        ll total = 0;
        memset(visit,false,sizeof(visit));
        dfs(1);
        for(int i = 0; i < cnt2; i++)
        {
            int x1 = Edge2[i].u;
            int x2 = Edge2[i].v;
            w = Edge2[i].w;

            ll v1 = min(dp[x1],dp[x2]);
            ll v2 = n - v1;
            total += v1*v2*w;

        }
        ll c =(ll)n*(ll)(n - 1)/2;
        double expe = (double)total/c;
        printf("%I64d %.2lf\n",sum,expe);
    }
    return 0;
}
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值