HDU6567 - Cotree

本题要求在两棵树间添加一条边以最小化所有节点间的距离总和。通过寻找树的重心来确定最优连接点,并使用DFS遍历求解最终的最小距离和。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Cotree

Time Limit: 2000/1000 MS (Java/Others)
Memory Limit: 262144/262144 K (Java/Others)

Problem Description

Avin has two trees which are not connected. He asks you to add an edge between them to make them connected while minimizing the function ∑ i = 1 n ∑ j = i + 1 n d i s ( i , j ) \sum_{i=1}^{n}\sum_{j=i+1}^{n}dis(i,j) i=1nj=i+1ndis(i,j) , where d i s ( i , j ) dis(i,j) dis(i,j) represents the number of edges of the path from i to j . He is happy with only the function value.

Input

The first line contains a number n (2<=n<=100000). In each of the following n−2 lines, there are two numbers u and v, meaning that there is an edge between u and v. The input is guaranteed to contain exactly two trees.

Output

Just print the minimum function value.

Sample Input

3
1 2

Sample Output

4

Tips

题意:

给定一个具有 n n n 个结点、 n − 2 n-2 n2 条边的图,保证其连通分量为 2 2 2 。现在请你添加一条边,使图连通,求连通图上任意两个结点的距离之和的最小值。

题解:

举几个例子就会发现,我们应该选择两棵树的树根将它们相连,且这两个树根使得这两棵树尽可能地“平衡”。事实上,这样的根节点叫做树的重心。其形式化的定义为:

以某结点为根节点的一棵或几棵子树中,结点个数的最大值称为该结点的 balance \text{balance} balance ,重心的 balance \text{balance} balance 是所有结点中最小的那个。

要找到树的重心,只需一遍 dfs \text{dfs} dfs ,统计出每个结点的子树分别含有多少结点,得到 balance \text{balance} balance ,选出 balance \text{balance} balance 最小的那个结点即可。找出两个重心以后,我们将它们相连,这就完成了这一题的第一部分(当然,在对树进行操作之前,还需要用并查集取出两个分别位于两棵树的结点)。

而本题的剩余部分,在于如何求解一棵树上任意两个结点的距离之和。区别于暴力,较优的解法为,考虑每条边被经过的次数,最后的答案即为次数乘以边权(本题为 1 1 1)的和。一条边被经过的次数,等于由它连接的两部分的结点的个数之积。而结点个数的求解方法和上述 dfs \text{dfs} dfs 函数一致。

因此,总结一下本题的解决过程就是:首先用并查集找出分别位于两棵树中的两个结点,然后对这两个结点分别进行 dfs \text{dfs} dfs ,找出两棵树的重心,将重心连接,再 dfs \text{dfs} dfs 一遍,最后计算出答案并输出即可。

Reference Code

#include <cstdio>
#include <cstring>
#include <cstring>
#include <algorithm>
using std::max;
typedef long long ll;
const int INF=0x3f3f3f3f;
const int MAXN=1e5+10;
const int MAXM=2e5+10;
int fa[MAXN],hi[MAXN];
int cnt[MAXN];
int Find(int v){
    return (fa[v]==v)?(v):(fa[v]=Find(fa[v]));
}
inline void Union(int a,int b){
    int r1=Find(a),r2=Find(b);
    if (r1==r2) return;
    if (hi[r1]<hi[r2]){
        fa[r1]=r2;
        cnt[r2]+=cnt[r1];
    }
    else if (hi[r1]>hi[r2]){
        fa[r2]=r1;
        cnt[r1]+=cnt[r2];
    }
    else{
        fa[r1]=r2;
        cnt[r2]+=cnt[r1];
        ++hi[r2];
    }
}
struct Edge{
    int to,next;
}edge[MAXM];
int tot,head[MAXN];
inline void init(){
    tot=0;
    memset(head,-1,sizeof(head));
    for (int i=0;i<MAXN;++i){
        fa[i]=i;
        cnt[i]=1;
    }
}
inline void add_edge(int u,int v){
    edge[tot]=(Edge){v,head[u]};
    head[u]=tot++;
    edge[tot]=(Edge){u,head[v]};
    head[v]=tot++;
}
int dp[MAXN];
int balance,root;
void dfs(int u,int pre,int n){
    dp[u]=1;
    int balan=0;
    for (int i=head[u];~i;i=edge[i].next){
        int v=edge[i].to;
        if (v==pre) continue;
        dfs(v,u,n);
        dp[u]+=dp[v];
        balan=max(balan,dp[v]);
    }
    balan=max(balan,n-dp[u]);
    if (balan<balance){
        balance=balan;
        root=u;
    }
}
int set_root(int rt,int n){
    balance=INF;
    dfs(rt,-1,n);
    return root;
}
int n;
int u,v;
int r1,n1,r2,n2;
int main(){
    init();
    scanf("%d",&n);
    for (int i=2;i<n;++i){
        scanf("%d%d",&u,&v);
        Union(u,v);
        add_edge(u,v);
    }
    r1=Find(1);
    for (int i=2;i<=n;++i){
        if (Find(i)!=r1)
            r2=Find(i);
    }
    n1=cnt[r1];
    n2=n-cnt[r1];
    r1=set_root(r1,n1);
    r2=set_root(r2,n2);
    add_edge(r1,r2);
    dfs(r1,-1,n1);
    ll res=0;
    for (int i=1;i<=n;++i)
        res+=(ll)dp[i]*(n-dp[i]);
    printf("%lld\n",res);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值