1. 程式人生 > >codeforces 391E2 (【Codeforces Rockethon 2014】E2)

codeforces 391E2 (【Codeforces Rockethon 2014】E2)

data -1 article trac name node namespace %d problems

題目:http://codeforces.com/problemset/problem/391/E2
題意:有三棵樹。每棵樹有ni個結點,加入兩條邊把這三棵樹連接起來,合並成一棵樹。使得合並的樹中隨意兩點之間的最短路徑
的和最大。


分析:
三棵樹要合並成一棵樹,則第一棵樹必須選擇一個點,如果為X。第二棵樹必須選擇兩個點,如果為Y1, Y2,第三棵樹必須選擇一個點,如果為Z
記第一棵樹中全部結點到X的路徑總和為:tot1
第二棵樹中全部結點到Y1,Y2的路徑總和分別為:tot2, tot3
第三棵樹中全部結點到Z的路徑總和為:tot4;
共同擁有四種情況:
1,每棵樹內部的結點之間的距離為常數。能夠求出樹中一個點到剩余全部點的路徑之和,把全部這種點的和相加再除以2就可以
2,第一棵樹和第二棵樹這兩棵樹全部結點之間的距離,如果第一棵樹選擇結點X,第二棵樹選擇的左結點位Y1,
則兩棵樹上隨意兩點a,b之間的距離。能夠表示為:d(a, b) = d(a, X) + 1 + d(b, Y1),
當中a為第一棵樹的隨意結點。b為第二棵樹的隨意結點。
固定點a,變換bj。因為第二棵樹有n2個結點,則這樣的情況下的總的路徑和為:(d(a, X) + 1) * n2 + sum(d(bj, Y1), j = 1, 2, ..., n2);
再變換ai,則終於得到的路徑和為:sum((d(ai, X) + 1) * n2 + sum(d(bj, Y1), j = 1, 2, ..., n2), i = 1, 2, ..., n1);
終於結果為:sum(d(ai, X), i = 1, 2, ..., n1) * n2 + n2 * n1 + sum(d(bi, Y1), j = 1, 2, ..., n2) * n1;
即tot1 * n2 + n2 * n1 + tot2 * n1;
3,第二棵樹和第三棵樹這兩棵樹全部結點之間的距離,類似情況2,得到的終於結果為:tot3 * n3 + n2 * n3 + tot4 * n2;

4,第一棵樹和第三棵樹全部結點之間的距離:每一條路徑都能夠表示為:d(a, c) = d(a, X) + 1 + d(Y1, Y2) + 1 + d(Z, c);
終於結果為:tot1 * n3 + tot4 * n1 + n1 * n3 * d(Y1, Y2) + 2 * n1 * n3;

綜上所述,得到合並後樹中結點之間的距離總和為:
sum = (n2 + n3) * tot1 + (n1 + n2) * tot4 + n1 * n2 + n2 * n3 + 2 * n1 * n3 + n1 * tot2
+ n3 * tot3 + n1 * n3 * d(Y1, Y2) + 三棵樹的內部路徑;
要使得總和最大。則tot1和tot4必須最大,上式中間部分為常數,則left = n1 * tot2 + n3 * tot3 + n1 * n3 * d(Y1, Y2)必須達到最大
在tot2達到最大的情況下,即Y1確定時。枚舉Y2使得left部分達到最大,就可以。


這過程中要枚舉三棵樹的位置。

#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
typedef long long LL;
const int MAXN = 100000 + 10;
struct Edge
{
    int y, next;
};
struct Tree
{
    LL n, head[MAXN], nodeCnt[MAXN], edgeCnt, dis[MAXN], pos, maxTot;
    LL curSum[MAXN], tot[MAXN], pathCnt;
    Edge edge[MAXN << 1];
    void addEdge(int x, int y)
    {
        edge[edgeCnt].y = y;
        edge[edgeCnt].next = head[x];
        head[x] = edgeCnt++;
    }

    void build(int n)
    {
        memset(head, -1, sizeof(head));
        this->n = n;
        int x, y;
        for(int i = 1; i < n; i++)
        {
            scanf("%d%d", &x, &y);
            addEdge(x, y);
            addEdge(y, x);
        }
    }

    /*獲得son這棵樹的結點數nodeCnt[son],包含該父結點。同一時候獲得son這棵樹中全部子結點到son的路徑之和,保存在curSum[son]
    當中curSum[son] = sum(curSum[yi] + nodeCnt[yi], i = 1, 2, ...),即全部子樹的最短路徑值加上子樹的全部點數的和
    */
    void dfs0(int son, int fa)
    {
        nodeCnt[son] = 1;
        curSum[son] = 0;
        int y;
        for(int i = head[son]; i != -1; i = edge[i].next)
        {
            y = edge[i].y;
            if(y == fa)
            {
                continue;
            }
            dfs0(y, son);
            //回溯。已經獲得子結點y的值
            nodeCnt[son] += nodeCnt[y];
            curSum[son] += curSum[y] + nodeCnt[y];
        }
    }

    //獲得tot[son],即全部點到son的路徑之和
    void dfs1(int son, int fa, LL faLeft)
    {
        //當前son所在子樹的路徑之和,加上其它剩余部分到son的路徑之和
        tot[son] = curSum[son] + faLeft;
        int y;
        for(int i = head[son]; i != -1; i = edge[i].next)
        {
            y = edge[i].y;
            if(y == fa)
            {
                continue;
            }
            /*要算全部結點到y的最短路徑之和。除了y所在子樹外,應該加入的值來源有三部分:
              son這棵樹原先應加上的值。即整棵大樹減去son子樹剩余部分到son的路徑和:faLeft,
              son這棵樹除了y這棵子樹全部結點到son的路徑值外剩余的路徑和:
              son這棵樹的最短路徑和 - y這棵樹的最短路徑和 - y這棵樹的結點數,即curSum[son] - curSum[y] - nodeCnt[y];
             整棵合並樹減去 y子樹剩余的結點數:n - nodeCnt[y]
            */
            dfs1(y, son, faLeft + curSum[son] - curSum[y] - nodeCnt[y] + n - nodeCnt[y]);
        }
    }

    //深度遍歷,獲得每一個結點的層次,即為到根結點的最短路徑,註意根結點層次為0
    void dfs2(int son, int fa)
    {
        dis[son] = dis[fa] + 1;
        int y;
        for(int i = head[son]; i != -1; i = edge[i].next)
        {
            y = edge[i].y;
            if(y == fa)
            {
                continue;
            }
            dfs2(y, son);
        }
    }

    void solve()
    {
        dfs0(1, 0);
        dfs1(1, 0, 0);
        //求出最大的單點最短路徑和,同一時候累加。即為這棵樹內部的路徑之和的兩倍
        for(int i = 1; i <= n; i++)
        {
            pathCnt += tot[i];
            if(tot[i] > maxTot)
            {
                maxTot = tot[i];
                pos = i;
            }
        }
        dis[0] = -1;
        dfs2(pos, 0);
    }
};

Tree t[3];

LL getAns(const Tree &t1, const Tree &t2, const Tree &t3)
{
    //先算好不變的部分
    LL tmp = (t2.n + t3.n) * t1.maxTot + (t1.n + t2.n) * t3.maxTot + t1.n * t2.n + t2.n * t3.n + 2 * t1.n * t3.n
             + (t1.pathCnt + t2.pathCnt + t3.pathCnt) / 2;
    LL ans, maxAns = 0;

    //固定Y1。t2.maxTot相當於tot2
    tmp += t1.n * t2.maxTot;

    //枚舉Y2
    for(int i = 1; i <= t2.n; i++)
    {
        //如果當前t2.tot[i]為tot3,t2.dis[i]為Y2到Y1的距離。Y1作為單原起點
        ans = (LL)tmp + t3.n * t2.tot[i] + t1.n * t3.n * t2.dis[i];
        maxAns = max(ans, maxAns);
    }
    return maxAns;
}

int main()
{
    int n[3], i, j;
    LL ans = 0;
    //freopen("in.txt", "r", stdin);
    scanf("%d%d%d", &n[0], &n[1], &n[2]);
    for(i = 0; i < 3; i++)
    {
        t[i].build(n[i]);
        t[i].solve();
    }

    //枚舉三棵樹的位置
    for(i = 0; i < 3; i++)
    {
        for(j = 0; j < 3; j++)
        {
            if(i == j)
            {
                continue;
            }
            ans = max(ans, getAns(t[i], t[j], t[3 - i - j]));
        }
    }
    printf("%I64d\n", ans);
    return 0;
}



參考博客:http://www.cnblogs.com/Delostik/p/3553114.html

codeforces 391E2 (【Codeforces Rockethon 2014】E2)