CodeForces 699D Fix a Tree

D. Fix a Tree
time limit per test
2 seconds
memory limit per test
256 megabytes
input
standard input
output
standard output

A tree is an undirected connected graph without cycles.

Let's consider a rooted undirected tree with n vertices, numbered 1 through n. There are many ways to represent such a tree. One way is to create an array with n integers p1, p2, ..., pn, where pi denotes a parent of vertex i (here, for convenience a root is considered its own parent).
For this rooted tree the array p is [2, 3, 3, 2].

Given a sequence p1, p2, ..., pn, one is able to restore a tree:

    There must be exactly one index r that pr = r. A vertex r is a root of the tree.
    For all other n - 1 vertices i, there is an edge between vertex i and vertex pi. 

A sequence p1, p2, ..., pn is called valid if the described procedure generates some (any) rooted tree. For example, for n = 3 sequences (1,2,2), (2,3,1) and (2,1,3) are not valid.

You are given a sequence a1, a2, ..., an, not necessarily valid. Your task is to change the minimum number of elements, in order to get a valid sequence. Print the minimum number of changes and an example of a valid sequence after that number of changes. If there are many valid sequences achievable in the minimum number of changes, print any of them.
Input

The first line of the input contains an integer n (2 ≤ n ≤ 200000) — the number of vertices in the tree.

The second line contains n integers a1, a2, ..., an (1 ≤ ai ≤ n).
Output

In the first line print the minimum number of elements to change, in order to get a valid sequence.

In the second line, print any valid sequence possible to get from (a1, a2, ..., an) in the minimum number of changes. If there are many such sequences, any of them will be accepted.
Examples
Input

4
2 3 3 4

Output

1
2 3 4 4 

Input

5
3 2 2 5 3

Output

0
3 2 2 5 3 

Input

8
2 3 5 4 1 6 6 7

Output

2
2 3 7 8 1 6 6 7

0x00

**這題我的第一種解法就是先將多個樹合併到一起,然後遍歷每一個節點,暴力搜尋環,類似dfs,不斷向父元素遞進,用一個set儲存途經的所有元素,對於查找到節點i時,判斷它的父元素在set中是否出現了,如果出現了就說明成了環,然後設置節點i的父節點爲一個統一的根節點
爲了優化速度,引入一個visited數組記錄某個節點是否已經訪問過,如果訪問過那麼下一次遇到的時候就直接結束向父元素的遞進**

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <set>

using namespace std;

// AC
//200ms

set<int> line;//緩存查找過程中經過的所有元素,耗時高

int n, changes = 0;
int root = 0;

int fa[200005];//這個數組作爲最後的輸出結果
int visited[200005];//優化

void check(int i) {//檢查是否有環出現

    if (!line.empty()) {
        line.clear();
    }

    while (fa[i] != i) {//未到達樹頂端時不斷循環

        visited[i] = 1;

        if (line.count(fa[i])) {//若找到環
            changes++;//改變次數+1
            if (!root) {//若還沒有根元素
                root = i;// 將此時的i作爲根元素
            }

            fa[i] = root;//把當前元素掛到根元素下

            return;//退出循環

        } else {//若這一步也沒出現環
            line.insert(i);//把當前元素加到set中
            i = fa[i];// 迭代
            if (visited[i]) { //若已經拜訪過了,就直接退出,這一步優化很關鍵!
                return;
            }
        }

    }

}

int main(int argc, char const *argv[])
{
    scanf("%d", &n);

    for (int i = 1; i <= n; ++i) {
        scanf("%d", fa + i);
        if (fa[i] == i) {
            if (root) { //若此前已經有根節點
                fa[i] = root;
                changes++;
            } else {
                root = i;
            }
        }
    }

    for (int i = 1; i <= n; ++i) {
        if (!visited[i]) {
            check(i);
        }
    }

    printf("%d\n", changes);

    printf("%d", fa[1]);

    for (int i = 2; i <= n; ++i) {
        printf(" %d", fa[i]);
    }

    printf("\n");
    return 0;
}

0x01

我的第二種解法是結合並查集

**思考
如果將一個樹中的某個節點i的父節點設置爲某個節點j,導致出現了環,那麼顯然從j沿着父節點的方向去遞推(用並查集的find函數),最終一定會到達i節點(先把所有沒有指定父節點的節點指向自己,即初始化fa[x] = x,這樣find(j)就會在i節點處結束)**

**也就是說當i = find(j)的時候,如果把i的父節點設置爲j,就會造成環的出現
抓住這一點,在環即將出現時,我們就可以把i的父節點改成指向統一的根節點,這樣就用一個操作就把環消除了**

**優化:
由於最終我們要輸出一個最少修改數量的數組,但是find函數的優化是建立在把大量節點直接掛到根節點下的這種操作上的,所以我建立了另一個數組icopy來讓find函數得以優化**

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <queue>

using namespace std;

//並查集實現
//108ms

/**
*   思路:
*   所有可能的情況就兩種,
*   1.正常的樹
*   2.成環
*   我們要做的是,把環拆開成樹,把所有樹並在一起,要做到操作次數最少,那麼當然是一個環在某一個地方拆開,指向另一個根節點,但是到底指向哪裏暫時還不知道,所以讓它先指向自己,稱爲假根節點,記錄下這些假節點,之後再來處理它的去向
*   如果有多個根節點,選其中一個作爲最終的根節點,其餘的根節點和假節點都並在它的下面[操作次數=假根節點的數量 + 根節點的數量 - 1]
*   如果沒有根節點,那麼某一個假節點作爲最終根節點[操作次數 = 假根節點數量 + 根節點數量(是0)]
*
*/

int n, changes = 0, root;

queue<int> fackRoot;

int fa[200005];//這個數組作爲最後的輸出結果
int facopy[200005];//這個數組作爲最後的輸出結果

void init() {
    for (int i = 1; i <= n; ++i) {
        facopy[i] = i;
    }
}

int find(int i) {//非遞歸實現
    int icopy = i;
    while (i != facopy[i]) {//找到根元素
        i = facopy[i];
    }

    while (icopy != facopy[icopy]) {
        icopy = facopy[icopy];//獲取父節點
        facopy[icopy] = i;//掛到根節點下
    }

    return i;
}

int main(int argc, char const *argv[])
{
    scanf("%d", &n);

    init();

    for (int i = 1; i <= n; ++i) {
        scanf("%d", fa + i);

        if (fa[i] == i) {
            if (root) {
                changes++;//做出改變
                fa[i] = root;
            } else {
                root = i;
            }
            facopy[i] = root;
        } else {
            if (i == find(fa[i])) {//若出現環
                //加入到fackRoot
                fackRoot.push(i);
                // facopy[i] = i;//拆開環,作爲假的根元素,暫時指向自己
                // 上面這一步操作不用寫,因爲之前對並查集執行過init()操作
            } else {
                facopy[i] = find(fa[i]);//直接掛在根節點
            }
        }
    }

    // 處理所有fackRoot
    changes += fackRoot.size();
    while (!fackRoot.empty()) {
        if (root) {//若之前確立了根元素
            fa[fackRoot.front()] = root;
            fackRoot.pop();
        } else {
            root = fackRoot.front();
        }
    }

    printf("%d\n", changes);

    printf("%d", fa[1]);

    for (int i = 2; i <= n; ++i) {
        printf(" %d", fa[i]);
    }

    printf("\n");
    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章