2019CCPC-江西省赛 Cotree(HDU-6567)

博客地址:https://startcraft.cn

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=6567

题目大意

给你两棵树,让你在两棵树之间加一条边,使得两棵树联通且任意两结点之间的距离之和最短
就是最小化这个式子
∑ i = 1 n ∑ j = i + 1 n d i s ( i , j ) \sum_{i=1}^n\sum_{j=i+1}^ndis\left(i,j\right) i=1nj=i+1ndis(i,j)

思路

首先分别对两棵树 T r e e A Tree_A TreeA T r e e B Tree_B TreeB,我们在 T r e e A Tree_A TreeA中找一个点 S A S_A SA,使得 T r e e A Tree_A TreeA中所有点到 S A S_A SA的距离之和最小,同理在 T r e e B Tree_B TreeB中找到 S B S_B SB,将这两个点连接起来,就是符合题目要求的边.
因为两棵树之间只有这一条边相连,那么 T r e e A Tree_A TreeA中的点要去 T r e e B Tree_B TreeB,就必须经过 S A , S B S_A,S_B SA,SB,又因为 S A , S B S_A,S_B SA,SB是各自的树里面的结点距离之和最小的,那么把它们连起来就是符合题意的
(其实 S A , S B S_A,S_B SA,SB就是树的重心)

那么怎样找 S A S_A SA呢,首先以任意一个顶点为根,假设以 1 1 1为根,维护两个数组 s u m [ i ] sum[i] sum[i],表示 i i i的子节点到 i i i的的距离之和, s u m _ n u m [ i ] sum\_num[i] sum_num[i]表示 i i i的子节点的个数,现在以 1 1 1为根做一遍DFS可以得到这两个数组。
然后开始换根,假设现在的根是 n o w now now, v v v n o w now now的子节点,那么当根从 n o w now now变到 v v v

  • s u m _ n u m [ n o w ] − = ( s u m _ n u m [ v ] + 1 ) sum\_num[now]-=(sum\_num[v]+1) sum_num[now]=(sum_num[v]+1) v v v v v v的子节点不再是 n o w now now的子节点,遂减去
  • s u m [ n o w ] − = ( s u m [ v ] + s u m _ n u m [ v ] + 1 ) sum[now]-=(sum[v]+sum\_num[v]+1) sum[now]=(sum[v]+sum_num[v]+1) v v v v v v的子节点不再走到 n o w now now,这部分的权值是它们走到 v v v的权值加上它们再走一步走到 n o w now now,再走一步走到 n o w now now的值就是结点个数 ∗ 1 *1 1,即 ( s u m [ v ] + s u m _ n u m [ v ] + 1 ) (sum[v]+sum\_num[v]+1) (sum[v]+sum_num[v]+1)
  • s u m _ n u m [ v ] + = s u m _ n u m [ n o w ] + 1 sum\_num[v]+=sum\_num[now]+1 sum_num[v]+=sum_num[now]+1 n o w now now n o w now now的子节点变成了 v v v的子节点,注意:这里的 s u m _ n u m [ n o w ] sum\_num[now] sum_num[now]已经是经过上面变换之后的
  • s u m [ v ] + = ( s u m [ n o w ] + s u m _ n u m [ n o w ] + 1 ) sum[v]+=(sum[now]+sum\_num[now]+1) sum[v]+=(sum[now]+sum_num[now]+1) n o w now now n o w now now的子节点将会走到 v v v,这部分的权值是它们走到 n o w now now的权值加上它们再走一步走到 v v v,再走一步走到 v v v的值就是结点个数 ∗ 1 *1 1,即 ( s u m [ n o w ] + s u m _ n u m [ n o w ] + 1 ) (sum[now]+sum\_num[now]+1) (sum[now]+sum_num[now]+1),注意:这里的关于 n o w now now的数组已经是经过前两步变换过的

然后遍历树中的所有结点我们就能得到以 S A S_A SA为根,最小的 s u m [ S A ] sum[S_A] sum[SA]
找出 S A , S B S_A,S_B SA,SB之后就可以计算题目中的那个式子了:
T r e e A Tree_A TreeA中的每一个点都要与 T r e e B Tree_B TreeB中的每一个点计算距离,那么首先 T r e e A Tree_A TreeA中的每一个点要先走到 S B S_B SB,距离就是 s u m [ S A ] + T r e e A sum[S_A]+Tree_A sum[SA]+TreeA中结点个数,这个式子的含义就是 T r e e A Tree_A TreeA中所有点先走到 S A S_A SA,再走一步到 S B S_B SB
又因为 T r e e B Tree_B TreeB中有 N N N个结点,所以 T r e e A Tree_A TreeA中的每一个点都要走 N N N次去 S B S_B SB这条路,所以要乘以 N N N
然后 S B S_B SB T r e e B Tree_B TreeB中所有点的距离之和是 s u m [ S B ] sum[S_B] sum[SB],这个值会被计算 T r e e A Tree_A TreeA中结点个数次,所以要乘以 T r e e A Tree_A TreeA中的结点个数

最后的结果就是
( s u m [ S A ] + s u m _ n u m [ S A ] + 1 ) ∗ ( s u m _ n u m [ S B ] + 1 ) + s u m [ S B ] ∗ ( s u m _ n u m [ S A ] + 1 ) (sum[S_A]+sum\_num[S_A]+1)*(sum\_num[S_B]+1)+sum[S_B]*(sum\_num[S_A]+1) (sum[SA]+sum_num[SA]+1)(sum_num[SB]+1)+sum[SB](sum_num[SA]+1)
其中 s u m _ n u m [ S A ] + 1 sum\_num[S_A]+1 sum_num[SA]+1 s u m _ n u m [ S B ] + 1 sum\_num[S_B]+1 sum_num[SB]+1就是 T r e e A Tree_A TreeA T r e e B Tree_B TreeB中的结点个数.
上面是两棵树之间点的距离之和,最后再加上两棵树各自内部的点之间的距离之和就行了,这个可以再换根的时候就得到换根时每一个点都作为根计算过 s u m [ i ] sum[i] sum[i],只需要将每个 s u m [ i ] sum[i] sum[i]累加起来最后除以2就行了

AC代码

#include <iostream>
#include <cstring>
#include <cstdio>
using namespace std;
typedef long long ll;
#define wfor(i,j,k) for(i=j;i<k;++i)
#define mfor(i,j,k) for(i=j;i>=k;--i)
// void read(ll &x) {
//  char ch = getchar(); x = 0;
//  for (; ch < '0' || ch > '9'; ch = getchar());
//  for (; ch >= '0' && ch <= '9'; ch = getchar()) x = x * 10 + ch - '0';
// }
const ll maxn = 100005;
struct EDGE
{
    ll next;
    ll end;
};
ll head[maxn];
EDGE edge[maxn * 2];
ll cnt;
void add(ll beg, ll end)
{
    edge[++cnt].next = head[beg];
    edge[cnt].end = end;
    head[beg] = cnt;
}
ll vis[maxn];
ll sum[maxn];
ll sum_num[maxn];
void dfs(ll now)//dfs先得到以任意一个结点为根的sum和sum_num数组的值
{
    ll i;
    ll temp_sum = 0;
    ll temp_sum_num = 0;
    for (i = head[now]; i; i = edge[i].next)
    {
        ll v = edge[i].end;
        if (!vis[v])
        {
            vis[v] = 1;
            dfs(v);
            temp_sum += sum_num[v] + sum[v] + 1;
            temp_sum_num += sum_num[v] + 1;
        }
    }
    sum_num[now] = temp_sum_num;
    sum[now] = temp_sum;
}
ll root_1;
ll sum_root1;
ll root_2;
ll sum_root2;
ll find_root(ll now, ll &root, ll &sum_root)//换根
{
    ll i;
    ll tot = 0;
    tot += sum[now];//累加以每一个点为根的sum[i]
    ll temp1 = sum[now];
    ll temp2 = sum_num[now];
    for (i = head[now]; i; i = edge[i].next)
    {
        ll v = edge[i].end;
        if (!vis[v])
        {
            vis[v] = 1;
	    /**四步变换**/
            sum[now] -= (sum[v] + sum_num[v] + 1);
            sum_num[now] -= (sum_num[v] + 1);
            sum[v] += sum[now] + sum_num[now] + 1;
            sum_num[v] += sum_num[now] + 1;
            if (sum[v] < sum_root)//找sum的最小值
            {
                sum_root = sum[v];
                root = v;
            }
            tot += find_root(v, root, sum_root);
            sum[now] = temp1;
            sum_num[now] = temp2;
        }
    }
    return tot;
}
int main()
{
    std::ios::sync_with_stdio(false);
#ifdef test
    freopen("F:\\Desktop\\question\\in.txt", "r", stdin);
#endif
#ifdef ubuntu
    freopen("/home/time/debug/debug/in", "r", stdin);
    freopen("/home/time/debug/debug/out", "w", stdout);
#endif
    ll n;
    cin >> n;
    ll i;
    wfor(i, 0, n - 2)
    {
        ll u, v;
        cin >> u >> v;
        add(u, v);
        add(v, u);
    }
    vis[1] = 1;
    dfs(1);
    root_1 = 1;
    sum_root1 = sum[root_1];
    wfor(i, 1, n + 1)
    {
        if (!vis[i])
        {
            vis[i] = 1;
            root_2 = i;
            dfs(i);
            break;
        }
    }
    sum_root2 = sum[root_2];
    memset(vis, 0, sizeof(vis));
    ll ans = 0;
    vis[root_1] = 1;
    ll temp = find_root(root_1, root_1, sum_root1);
    ans += temp / 2;
    vis[root_2] = 1;
    temp = find_root(root_2, root_2, sum_root2);
    ans += temp / 2;
    /**两棵树之间的距离**/
    ans += (sum_root2) * (sum_num[root_1] + 1) + (sum_root1 + sum_num[root_1] + 1) * (sum_num[root_2] + 1);
    cout << ans << endl;
    return 0;
}

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值