题目链接: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=1∑nj=i+1∑ndis(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;
}