题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=6567
题目大意
给你两棵树,让你在两棵树之间加一条边,使得两棵树联通且任意两结点之间的距离之和最短
就是最小化这个式子
思路
首先分别对两棵树和,我们在中找一个点,使得中所有点到的距离之和最小,同理在中找到,将这两个点连接起来,就是符合题目要求的边.
因为两棵树之间只有这一条边相连,那么中的点要去,就必须经过,又因为是各自的树里面的结点距离之和最小的,那么把它们连起来就是符合题意的
(其实就是树的重心)
那么怎样找呢,首先以任意一个顶点为根,假设以为根,维护两个数组,表示的子节点到的的距离之和,表示的子节点的个数,现在以为根做一遍DFS可以得到这两个数组。
然后开始换根,假设现在的根是, 是的子节点,那么当根从变到时
- 即及的子节点不再是的子节点,遂减去
- 即及的子节点不再走到,这部分的权值是它们走到的权值加上它们再走一步走到,再走一步走到的值就是结点个数,即
- 即及的子节点变成了的子节点,注意:这里的已经是经过上面变换之后的
- 即及的子节点将会走到,这部分的权值是它们走到的权值加上它们再走一步走到,再走一步走到的值就是结点个数,即,注意:这里的关于的数组已经是经过前两步变换过的
然后遍历树中的所有结点我们就能得到以为根,最小的
找出之后就可以计算题目中的那个式子了:
中的每一个点都要与中的每一个点计算距离,那么首先中的每一个点要先走到,距离就是中结点个数,这个式子的含义就是中所有点先走到,再走一步到
又因为中有个结点,所以中的每一个点都要走次去这条路,所以要乘以
然后到中所有点的距离之和是,这个值会被计算中结点个数次,所以要乘以中的结点个数
最后的结果就是
其中和就是和中的结点个数.
上面是两棵树之间点的距离之和,最后再加上两棵树各自内部的点之间的距离之和就行了,这个可以再换根的时候就得到换根时每一个点都作为根计算过,只需要将每个累加起来最后除以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;
}