題目鏈接: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;
}