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

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

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

題目大意

給你兩棵樹,讓你在兩棵樹之間加一條邊,使得兩棵樹聯通且任意兩結點之間的距離之和最短
就是最小化這個式子
i=1nj=i+1ndis(i,j) \sum_{i=1}^n\sum_{j=i+1}^ndis\left(i,j\right)

思路

首先分別對兩棵樹TreeATree_ATreeBTree_B,我們在TreeATree_A中找一個點SAS_A,使得TreeATree_A中所有點到SAS_A的距離之和最小,同理在TreeBTree_B中找到SBS_B,將這兩個點連接起來,就是符合題目要求的邊.
因爲兩棵樹之間只有這一條邊相連,那麼TreeATree_A中的點要去TreeBTree_B,就必須經過SA,SBS_A,S_B,又因爲SA,SBS_A,S_B是各自的樹裏面的結點距離之和最小的,那麼把它們連起來就是符合題意的
(其實SA,SBS_A,S_B就是樹的重心)

那麼怎樣找SAS_A呢,首先以任意一個頂點爲根,假設以11爲根,維護兩個數組sum[i]sum[i],表示ii的子節點到ii的的距離之和,sum_num[i]sum\_num[i]表示ii的子節點的個數,現在以11爲根做一遍DFS可以得到這兩個數組。
然後開始換根,假設現在的根是nownow, vvnownow的子節點,那麼當根從nownow變到vv

  • sum_num[now]=(sum_num[v]+1)sum\_num[now]-=(sum\_num[v]+1)vvvv的子節點不再是nownow的子節點,遂減去
  • sum[now]=(sum[v]+sum_num[v]+1)sum[now]-=(sum[v]+sum\_num[v]+1)vvvv的子節點不再走到nownow,這部分的權值是它們走到vv的權值加上它們再走一步走到nownow,再走一步走到nownow的值就是結點個數1*1,即(sum[v]+sum_num[v]+1)(sum[v]+sum\_num[v]+1)
  • sum_num[v]+=sum_num[now]+1sum\_num[v]+=sum\_num[now]+1nownownownow的子節點變成了vv的子節點,注意:這裏的sum_num[now]sum\_num[now]已經是經過上面變換之後的
  • sum[v]+=(sum[now]+sum_num[now]+1)sum[v]+=(sum[now]+sum\_num[now]+1)nownownownow的子節點將會走到vv,這部分的權值是它們走到nownow的權值加上它們再走一步走到vv,再走一步走到vv的值就是結點個數1*1,即(sum[now]+sum_num[now]+1)(sum[now]+sum\_num[now]+1),注意:這裏的關於nownow的數組已經是經過前兩步變換過的

然後遍歷樹中的所有結點我們就能得到以SAS_A爲根,最小的sum[SA]sum[S_A]
找出SA,SBS_A,S_B之後就可以計算題目中的那個式子了:
TreeATree_A中的每一個點都要與TreeBTree_B中的每一個點計算距離,那麼首先TreeATree_A中的每一個點要先走到SBS_B,距離就是sum[SA]+TreeAsum[S_A]+Tree_A中結點個數,這個式子的含義就是TreeATree_A中所有點先走到SAS_A,再走一步到SBS_B
又因爲TreeBTree_B中有NN個結點,所以TreeATree_A中的每一個點都要走NN次去SBS_B這條路,所以要乘以NN
然後SBS_BTreeBTree_B中所有點的距離之和是sum[SB]sum[S_B],這個值會被計算TreeATree_A中結點個數次,所以要乘以TreeATree_A中的結點個數

最後的結果就是
(sum[SA]+sum_num[SA]+1)(sum_num[SB]+1)+sum[SB](sum_num[SA]+1) (sum[S_A]+sum\_num[S_A]+1)*(sum\_num[S_B]+1)+sum[S_B]*(sum\_num[S_A]+1)
其中sum_num[SA]+1sum\_num[S_A]+1sum_num[SB]+1sum\_num[S_B]+1就是TreeATree_ATreeBTree_B中的結點個數.
上面是兩棵樹之間點的距離之和,最後再加上兩棵樹各自內部的點之間的距離之和就行了,這個可以再換根的時候就得到換根時每一個點都作爲根計算過sum[i]sum[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;
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章