最小生成树+树上任意两点的最小期望

参考博客

题意:有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<stdio.h>
#include<iostream>
#include<algorithm>
#include<vector>
#include<string.h>
typedef long long ll;
using namespace std;
const int max_=1e6+5;
const int max_n=1e5+5;
struct edge{
     int form;
     int to;
     ll w;
};
struct edge tu[max_],tree[max_n];
int dp[max_n];//记录包自身的子树节点
int fa[max_n];//父节点
bool vis[max_n];//标记数组
vector<int>g[max_n];//记录子节点
int tot1,tot2;
void add_edge1(int x,int y,ll w)//建图
{
    tu[tot1].form=x;
    tu[tot1].to=y;
    tu[tot1++].w=w;
}
void add_edge2(int x,int y,ll w)//建树
{
    tree[tot2].form=x;
    tree[tot2].to=y;
    tree[tot2++].w=w;
}
bool cmp(edge a,edge b)//排序
{
    return a.w<b.w;
}
int find_fa(int x)//并查集
{
    if(x==fa[x])
        return x;
    else
        return fa[x]=find_fa(fa[x]);
}
ll Kruskal(int n)//最小生成树算法
{
    sort(tu,tu+tot1,cmp);
    int ans=0;
    ll ant=0;
    for(int i=0;i<tot1;i++)
    {
        int v=tu[i].form;
        int u=tu[i].to;
        ll w=tu[i].w;
        int f1=find_fa(v);
        int f2=find_fa(u);
        if(f1!=f2)
        {
            fa[f1]=f2;
            ant+=w;
            add_edge2(u,v,w);
            ans++;
            g[u].push_back(v);
            g[v].push_back(u);
        }
        if(ans==n-1)
        {
        return ant;
        }
    }
}
void dfs(int x)//dfs寻找树的子节点
{
    vis[x]=true;
    dp[x]=1;
    for(int i=0;i<g[x].size();i++)
    {
        int y=g[x][i];
        if(!vis[y])
        {
            dfs(y);
            dp[x]+=dp[y];
        }
    }
}
void init(int n)//初始化
{
    memset(vis,0,sizeof(vis));
    memset(dp,0,sizeof(dp));
    for(int i=1;i<=n;i++)
    {
          fa[i]=i;
          g[i].clear();
    }
    tot1=0;
    tot2=0;
}
int main()
{
    int t;
    scanf("%d",&t);
    while(t--)
    {
        int n,m;
      scanf("%d %d",&n,&m);
        ll ans=0;
        init(n);
        if(m==0)
        {
              printf("0 0.00
");
              continue;
        }
        while(m--)
        {
            int x,y;
            ll w;
            scanf("%d %d %lld",&x,&y,&w);
            add_edge1(x,y,w);
        }
        ll sum=Kruskal(n);
        dfs(1);
        for(int i=0;i<tot2;i++)
        {
            int u=tree[i].form;
            int v=tree[i].to;
            ll w=tree[i].w;
            int k=min(dp[v],dp[u]);
            ans+=w*(n-k)*k;
            //cout<<ans<<endl;
        }
       // printf("%.2f
",1.0*(n-1)*n/2);
        double expe=ans*1.0/(1.0*n*(n-1)/2);
        printf("%lld %.2lf
",sum,expe);
    }
}
原文地址:https://www.cnblogs.com/linhaitai/p/9745275.html