Abandoned country HDU - 5723 (???)

An abandoned country has n(n≤100000)n(n≤100000) villages which are numbered from 1 to nn. Since abandoned for a long time, the roads need to be re-built. There are m(m≤1000000)m(m≤1000000) roads to be re-built, the length of each road is wi(wi≤1000000)wi(wi≤1000000). Guaranteed that any two wiwi 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)T(T≤10) which indicates the number of test cases.

For each test case, the first line contains two integers n,mn,m indicate the number of villages and the number of roads to be re-built. Next mm lines, each line have three number i,j,wii,j,wi, the length of a road connecting the village ii and the village jj is wiwi.
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
这道题,粗略看了一下,感觉还是有点难度,因为在于求期望上,假设去枚举路径上,那么,一共是 C2n 个不同的路径,那是至少已经到了 n2 的复杂度了,事实上我也的确可以在 O(n) 预处理下,在 O(1) 时间得到任意两个点的距离(在一颗树下),但是 O(n2) 的复杂度下,100000个点是不可能行的通的,然后就是转换角度,不去枚举路径,转而去计算边对总和的贡献,就是说,有多少不同的路径包括这条边,那怎么知道?观察其实发现,只要知道把这条边去掉,那么这颗树就变成两个连通分量,那么这条边的贡献就是两个连通分量的点的个数的乘积再乘这条边的权值。

其实真的acm里面有一类的题都是这种转化枚举角度的,另一个角度看就是另一个世界…感觉算是一类题型了,但是后来我又卡在了怎么计算一条边的一个端点有多少点, 后来在队友提醒下去计算这个点有多少子节点就是子树的大小,然后另一边的点数就总点数减一下就好
那直接dfs一次就可以知道每个点有多少个儿子(前提是得确定以那个点为根,一般我都用1为root),后来在写的时候发现那只要在计算son的时候顺便计算边的贡献就好了

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <string>
#include <cstring>
#include <vector>
#include <algorithm>
using namespace std;
int n,m;
vector<int> g[100005];
struct node
{
    int x1,x2;
    int w;
}edge[1000005];
int cmp(node x1,node x2)
{
    return x1.w<x2.w;
}
int pre[100005];
int findd(int x)//并查集
{
    return pre[x]==x?x:pre[x]=findd(pre[x]);
}
int k;
int son[100005];//在点1为root的情况下,每个点的为代表的子树的大小
double ans=0;
void dfs(int v,int fa)
{
    for(int i=0;i<g[v].size();i++)
    {
        int index=g[v][i];
        int u=edge[index].x1==v?edge[index].x2:edge[index].x1;
        if(fa==u)//不能和父亲节点相同
            continue;
        dfs(u,v);//dfs的同时就已经计算完毕u子树大小
        son[v]+=son[u];//计算v子树大小
        ans+=(double)son[u]*(n-son[u])*edge[index].w;//计算边的贡献
    }
    son[v]++;//自己本身算一个点
}
int main (void)
{
    //ios::sync_with_stdio(false);
    int t;
    int x,y,w;
    scanf("%d",&t);
    while(t--)
    {
        scanf("%d%d",&n,&m);
        if (n == 1) {
            printf ("0 0.00\n");
            continue;
        }
        ans=0;
        memset(son,0,sizeof(son));
        for(int i=1;i<=n;i++)
            g[i].clear();
        for(int i=0;i<m;i++)
        {
            scanf("%d%d%d",&x,&y,&w);
            edge[i].x1=x;
            edge[i].x2=y;
            edge[i].w=w;
        }
        sort(edge,edge+m,cmp);
        for(int i=1;i<=n;i++)
            pre[i]=i;
        long long cost=0;
        int countt=0;
        for(int i=0;i<m;i++)//并查集找最小生成树
        {
            int xx=findd(edge[i].x1);
            int yy=findd(edge[i].x2);
            if(xx!=yy)并查集找最小生成树,于此同属建树
            {
                countt++;
                pre[xx]=yy;
                g[edge[i].x1].push_back(i);我的最近养成的建图风格,加入边的index
                g[edge[i].x2].push_back(i);
                cost+=edge[i].w;
                if(countt==n-1)
                    break;
            }
        }
        dfs(1,-1);//写1到n之间的点都可以
        //cout<< ans << ' ' << n*(n-1) << ' ' << 2 * ans/(n*(n-1)) <<endl;
        printf("%lld %.2lf\n", cost, 2 * ans/(n*(n-1.0)) );//这里巨坑,没发现n*n会超int,wa两发,ba 1变成1.0就好...
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值