1. 程式人生 > 資訊 >微軟釋出新款粉色 Xbox 手柄

微軟釋出新款粉色 Xbox 手柄

介紹

點分治是用來解決樹上路徑問題的一種方法。

在解決樹上路徑問題時,我們可以選取一點為根,將樹轉化為有根樹,然後考慮經過根的所有路徑(有時將兩條從根出發的路徑連線為一條)。統計完這些路徑的答案後,將根節點標記為刪除,對剩下的若干棵樹進行同樣的操作。

如圖,我們可以先考慮經過節點 1 的路徑,之後將節點 1 標記為刪除,此時可以認為考慮過的路徑均已被刪除。繼續對其它子樹做相同處理即可。

每次確認一個根節點後,共有 n 條需要考慮的路徑( n 為當前子樹大小)。上圖中將 1 刪除後,剩下左側的子樹較大,和原樹大小相當,繼續處理這棵子樹時仍然需要與前一過程相當的時間。

最嚴重的情況,當整棵樹是一條鏈時,每次需要考慮的路徑數量是 O(n) 級別的,如果每條路徑需要常數時間進行統計,則總時間複雜度為 O(n^2) 。而對於形態隨機的樹,則遠遠小於這個級別。

如果我們選擇 5 作為這棵樹的根節點,情況會好很多 —— 刪除 5 後剩餘的最大一棵子樹的大小比刪除 1 時要小。這說明「科學地」選擇點作為根節點可以有效的降低複雜度。

重心方面操作

模板

struct Node
{
    struct Edge *firstEdge;
    // solved 表示該節點是否已被解決
    // 在點分治中,標記 solved 的節點被認為不存在
    //
    // vis 表示在當前 DFS / BFS 中是否訪問過
    bool solved, vis;
    // size 表示子樹大小(和樹剖中相同)
    // max 表示最大子節點大小
    int size, max;
    Node *fa; // 父節點
} N[MAXN + 1];

// 找以 start 為根的子樹的重心
// 非遞迴 DFS
inline Node *center(Node *start)
{
    std::stack<Node *> s;
    s.push(start);
    start->vis = false;
    start->fa = NULL;

    static Node *a[MAXN + 1]; // 儲存所有 DFS 到的節點
    int cnt = 0;
    while (!s.empty())
    {
        Node *v = s.top();

        // 如果是第一次出棧,則不將 v 從棧中刪除
        // 將所有子節點入棧
        if (!v->vis)
        {
            a[++cnt] = v; // 記錄節點

            v->vis = true;
            for (Edge *e = v->firstEdge; e; e = e->next)
            {
                // 判斷不走回父節點,不走到已經 solved 的節點
                if (e->to != v->fa && !e->to->solved)
                {
                    e->to->fa = v;
                    e->to->vis = false;
                    // 子節點入棧
                    s.push(e->to);
                }
            }
        }
        else
        {
            // 第二次出棧,表示回溯到 v
            v->size = 1;
            v->max = 0;
            for (Edge *e = v->firstEdge; e; e = e->next)
            {
                if (e->to->fa == v)
                {
                    // 維護 size 和 max
                    v->size += e->to->size;
                    v->max = std::max(v->max, e->to->size);
                }
            }

            // 將 v 從棧中刪除
            s.pop();
        }
    }

    // 統計重心
    Node *res = NULL;
    for (int i = 1; i <= cnt; i++)
    {
        // v->max 表示在整棵子樹中,刪掉 v 後剩餘的最大子樹
        // 如果把 v 作為根,則原有的除 v 的子樹以外的部分會成為 v 的一棵子樹
        // 這部分的大小為 總節點數量 - v->size
        // 因為是以 start 作為根進行的 BFS,總節點數量即為 start->size
        a[i]->max = std::max(a[i]->max, start->size - a[i]->size);

        // 更新答案
        if (!res || res->max > a[i]->max) res = a[i];
    }

    return res;
}

// 主求解過程
inline int solve()
{
    std::stack<Node *> s;
    s.push(&N[1]);

    int ans = 0; // 答案
    while (!s.empty())
    {
        // 這裡的 DFS 不需要回溯,所以每次出棧即可
        Node *v = s.top();
        s.pop();

        // 求重心
        Node *root = center(v);

        // 為防止後續的 BFS、DFS 走回根,先將根置為 solved
        root->solved = true;

        ans += calc(root);
    }

    return ans;
}