HDU 2586 How far away ?——樹上節點最短距離,LCA, 雙親表示法+暴力從下至上追溯,孩子鏈表示法+(Tarjan 或 歐拉環遊RMQ+(ST 或 SegmentTree))

標題真長。。。

http://acm.hdu.edu.cn/showproblem.php?pid=2586

四種解法:

- 雙親表示法+暴力從下至上追溯
- 孩子鏈表示法+Tarjan
- 孩子鏈表示法+歐拉環遊RMQ+ST
- 孩子鏈表示法+歐拉環遊RMQ+SegmentTree

對於建樹的問題,要解決父節點和子節點的問題:

- 第一種解法中,雙親表示法,用一個一維數組houses來儲存所有節點,houses[x].fa表示該節點的父節點,當兩個子樹被合併造成衝突時,將其中一棵樹倒置

如:

1 2
↑ ↑
3 4
↑ ↑
5 6

此時要連接3和4,必定會造成衝突,因爲,若將3作爲4的父節點(3 → 4),4就會有兩個父節點,於是把4 ← 6這一支倒置成 4 → 6
於是:

1 2
↑ ↑
3 → 4
↑ ↓
5 6
(5成爲合併以後的根元素)

- 剩下三種解法則利用孩子鏈表示法,記錄所有與目標節點相連接的節點(包括一個父節點和一個子節點),然後隨便選取一個節點作爲父節點,用dfs遍歷這些連接的節點,同時用visited數組來跳過其中的父節點

解法1:

#include <iostream>
#include <algorithm>
#include <cstring>
#include <string>
#include <cstdio>
#include <cmath>

using namespace std;

//31ms

//雙親表示法

//當建樹遇到衝突時,將衝突的一支樹倒置

struct Node
{
    int disToFa;
    int fa;
};

Node houses[40005];
int visited[40005];

void add(int x, int y, int z) {
    if (!houses[x].fa) {//若x還沒有父元素
        //把x掛在y下
        houses[x].fa = y;
        houses[x].disToFa = z;

    } else {

        if (!houses[y].fa) { //若y還沒有父元素
            //把y掛在x下
            houses[y].fa = x;
            houses[y].disToFa = z;
        } else {
            //x和y都有父元素了

            //將x的父元素向上追溯全部變成子元素(將這一支箭頭全部倒置)

            //修改前的副本
            Node temp_x = houses[x];
            Node temp_x_fa = houses[temp_x.fa];

            //把x掛在y下
            houses[x].fa = y;
            houses[x].disToFa = z;

            while (temp_x_fa.fa) {

                houses[temp_x.fa].disToFa = temp_x.disToFa;
                houses[temp_x.fa].fa = x;

                x = temp_x.fa;
                temp_x = houses[x];
                temp_x_fa = houses[temp_x.fa];

            }

        }

    }
}

int cal(int x, int y) {
//先從x開始一直追溯到根節點,沿途標記所有經過的節點(visited數組兩個作用,一是用來標記是否訪問過,二是用來記錄從x節點出發以後走過的距離)
    int sum_x = 0;
    visited[x] = -1;

    sum_x += houses[x].disToFa;
    x = houses[x].fa;

    while (houses[x].fa) {//當未到樹頂時
        visited[x] = sum_x;
        sum_x += houses[x].disToFa;
        x = houses[x].fa;
    }

    //此時x是樹頂
    if (visited[x] != -1) {
        visited[x] = sum_x;
    }

    //接下來從y開始向上追溯
    int sum_y = 0;
    while (!visited[y]) {
        sum_y += houses[y].disToFa;
        y = houses[y].fa;
    }

    //根據之前留下的-1判斷原始的x是否爲y的父元素
    if (visited[y] == -1) {//這種情況表明y向上追溯的過程中遇到了x
        return sum_y;//直接返回y向上追溯到x的距離
    } else {//這種情況表明y追溯到了x的某一個祖先元素
        return sum_y + visited[y];//返回y向上追溯到x的距離 + 從x到這個祖先元素的距離
    }

}

int main(int argc, char const *argv[])
{
    int T, n, m, cp_n, a1, a2, a3;
    scanf("%d", &T);
    while (T--) {
        scanf("%d%d", &n, &m);

        cp_n = n;

        memset(houses, 0, sizeof(Node) * (n + 1));

        while (--n) {
            scanf("%d%d%d", &a1, &a2, &a3);

            add(a1, a2, a3);

        }

        while (m--) {
            scanf("%d%d", &a1, &a2);

            memset(visited, 0, sizeof(int) * (cp_n + 1));//每處理一個問題前刷新一次

            printf("%d\n", cal(a1, a2));

        }

    }

    return 0;
}

解法2:

#include <iostream>
#include <algorithm>
#include <cstring>
#include <string>
#include <cstdio>
#include <cmath>

using namespace std;

//31ms
//Tarjan

//孩子鏈表示法(鏈表)
//這裏的孩子不一定是子節點,可能還有一個父節點,但是可以用visited數組來區別
//隨便選取一個hand作爲根節點,就可以建成一棵樹

int hand[40005];//保存第x號房子的所有孩子節點鏈表的起始節點編號

struct Node//鏈表節點
{
    int distance;//權值
    int to;
    int next;//保存下一個Node的位置(並非指房子的編號,是節點的編號),即孩子組成的鏈表中的下一個節點的位置
};

Node nodes[40005 << 1];//需要兩倍空間
int pos;//pos爲nodes的當前可用的Node的位置編號
int disToRoot[40005];//代表到根節點的距離
// 結果 = disToRoot[x] + disToRoot[y] - 2 * disToRoot[LCA(x, y)]

//Tarjan
int fa[40005];
int visited[40005];
int qhand[40005];
Node ques[40005 << 1];
int qpos;

void addToTree(int x, int y, int z) {//表示爲x號房子添加一個子節點 y ,距離爲z
    //爲pos號節點寫入數據
    nodes[pos].to = y;
    nodes[pos].distance = z;
    nodes[pos].next = hand[x];
    hand[x] = pos;

    pos++;//當前可用的節點編號+1
}

void addToQue(int x, int y) {
    ques[qpos].to = y;
    ques[qpos].distance = 0;
    ques[qpos].next = qhand[x];
    qhand[x] = qpos;

    qpos++;
}

int tarjanFind(int x) {//並查集查找(非遞歸壓縮路徑)
    int cp_x = x;
    while (fa[x] != x) {
        x = fa[x];
    }

    while (fa[cp_x] != cp_x) {
        cp_x = fa[cp_x];
        fa[cp_x] = x;
    }

    return x;
}

void tarjan(int which) {

    visited[which] = 1;

    fa[which] = which;

    int childPos = hand[which];

    while (childPos) {

        if (visited[nodes[childPos].to]) {//跳過父元素
            childPos = nodes[childPos].next;
            continue;
        }

        disToRoot[nodes[childPos].to] = disToRoot[which] + nodes[childPos].distance;//寫入到根節點的距離
        tarjan(nodes[childPos].to);

        fa[nodes[childPos].to] = which;

        childPos = nodes[childPos].next;

    }

//處理詢問
    int quesPos = qhand[which];

    while (quesPos) {

        if (visited[ques[quesPos].to]) {
            ques[quesPos].distance = disToRoot[which] + disToRoot[ques[quesPos].to] - 2 * disToRoot[tarjanFind(ques[quesPos].to)];
        }

        quesPos = ques[quesPos].next;
    }

}

int main(int argc, char const *argv[])
{
    int T, n, m, a1, a2, a3;
    scanf("%d", &T);
    while (T--) {
        scanf("%d%d", &n, &m);

        memset(hand, 0, sizeof(int) * (n + 1));
        memset(qhand, 0, sizeof(int) * (n + 1));
        memset(visited, 0, sizeof(int) * (n + 1));

        pos = 1;

        while (--n) {
            scanf("%d%d%d", &a1, &a2, &a3);

            addToTree(a1, a2, a3);
            addToTree(a2, a1, a3);

        }

        qpos = 1;

        //離線算法(先收集所有問題,然後統一遍歷)
        while (m--) {

            scanf("%d%d", &a1, &a2);

            //建立鏈表的方法和上面建樹的方法類似
            addToQue(a1, a2);
            addToQue(a2, a1);

        }

        disToRoot[1] = 0;
        tarjan(1);

        for (int i = 1; i < qpos; i += 2) {
            printf("%d\n", ques[i].distance ? ques[i].distance : ques[i + 1].distance);
        }

    }

    return 0;
}

解法3:

#include <iostream>
#include <algorithm>
#include <cstring>
#include <string>
#include <cstdio>
#include <cmath>

using namespace std;

//46ms
//轉化爲RMQ問題_ST
//在線算法

//孩子鏈表示法(鏈表)
//這裏的孩子不一定是子節點,可能還有一個父節點,但是可以用visited數組來區別
//隨便選取一個hand作爲根節點,就可以建成一棵樹

int hand[40005];//保存第x號房子的所有孩子節點鏈表的起始節點編號

struct Node//鏈表節點
{
    int distance;//權值
    int to;
    int next;//保存下一個Node的位置(並非指房子的編號,是節點的編號),即孩子組成的鏈表中的下一個節點的位置
};

Node nodes[40005 << 1];//需要兩倍空間

int pos;//pos爲nodes的當前可用的Node的位置編號
int disToRoot[40005];//代表到根節點的距離
// 結果 = disToRoot[x] + disToRoot[y] - 2 * disToRoot[LCA(x, y)]

//RMQ
int rmq_which[40005 << 1];//RMQ數組長度約爲節點數的兩倍(實際上是2n-1),儲存歐拉環遊經過的所有節點號
int rmq_deep[40005 << 1];//儲存環遊中節點的深度
int rmq_first[40005];//儲存循環中 x 號節點(房子)第一次出現的位置
int rmq_pos;

int visited[40005];//排除父節點

//ST
int st[40005 << 1][18]; //2的17次方大於 40005
// st[x][y] = 代表rmq_deep數組中從x開始持續長爲(2的y次方)長的區間範圍內的最小deep 的位置

void addToTree(int x, int y, int z) {//表示爲x號房子添加一個子節點 y ,距離爲z
    //爲pos號節點寫入數據
    nodes[pos].to = y;
    nodes[pos].distance = z;
    nodes[pos].next = hand[x];
    hand[x] = pos;

    pos++;//當前可用的節點編號+1
}

/**
* rmq函數用歐拉環遊生成了rmq_which 和 rmq_deep 和 rmq_first 和 disToRoot 四個數組
*/
void rmq(int which, int deep) {
    visited[which] = 1;

    rmq_which[rmq_pos] = which;
    rmq_deep[rmq_pos] = deep;
    rmq_first[which] = rmq_pos;
    rmq_pos++;

    int childPos = hand[which];

    while (childPos) {
        if (visited[nodes[childPos].to]) {
            childPos = nodes[childPos].next;
            continue;
        }

        disToRoot[nodes[childPos].to] = disToRoot[which] + nodes[childPos].distance;

        rmq(nodes[childPos].to, deep + 1);

        rmq_which[rmq_pos] = which;
        rmq_deep[rmq_pos] = deep;
        rmq_pos++;

        childPos = nodes[childPos].next;

    }

}

//初始化st數組
void init_st() {
    for (int i = 0; i < rmq_pos; ++i) {//根據st的定義,y爲0時,st[i][0] = i;
        st[i][0] = i;
    }

    for (int y = 1 ; y < 18; ++y) {
        for (int i = 0; i + (1 << y) - 1 < rmq_pos; ++i) {
            st[i][y] =  rmq_deep[st[i][y - 1]] < rmq_deep[st[i + (1 << (y - 1))][y - 1]] ? st[i][y - 1] : st[i + (1 << (y - 1))][y - 1];
        }
    }
}

// 返回rmq_deep數組區間[x, y]之間的最小元素的位置
int min_st(int x, int y) {

    if (x > y) {//保證輸入的x <= y;若不滿足,則反過來
        return min_st(y, x);
    }

    for (int i = 17; i >= 0; --i) {
        if (x + (1 << i) - 1 > y) {
            continue;
        }

        if (x + (1 << i) - 1 == y) {
            return st[x][i];
        } else {
            int temp = min_st(x + (1 << i), y);
            return rmq_deep[st[x][i]] < rmq_deep[temp] ? st[x][i] : temp;
        }
    }

    return 0;
}

int main(int argc, char const *argv[])
{
    int T, n, m, a1, a2, a3, first = 1;
    scanf("%d", &T);
    while (T--) {
        scanf("%d%d", &n, &m);

        memset(hand, 0, sizeof(int) * (n + 1));
        memset(visited, 0, sizeof(int) * (n + 1));

        pos = 1;

        while (--n) {
            scanf("%d%d%d", &a1, &a2, &a3);

            addToTree(a1, a2, a3);
            addToTree(a2, a1, a3);

        }

        disToRoot[1] = 0;

        //在線算法(逐個回答問題)

        rmq_pos = 0;

        rmq(1, 1);

        init_st();

        if (first) {
            first = 0;
        } else {
            printf("\n");
        }

        // for (int i = 0; i < rmq_pos; i++) {
        //  printf("%d ", rmq_which[i]);
        // }
        // printf("\n");
        // for (int i = 0; i < rmq_pos; i++) {
        //  printf("%d ", rmq_deep[i]);
        // }
        // printf("\n");
        // for (int i = 1; i <= 6; i++) {
        //  printf("%d ", rmq_first[i]);
        // }
        // printf("\n");

        // for (int x = 0; x < 18; x++) {
        //  for (int i = 0; i < rmq_pos; i++) {
        //      printf("%d ", st[i][x]);
        //  }
        //  printf("\n");
        // }

        while (m--) {

            scanf("%d%d", &a1, &a2);

            printf("%d\n", disToRoot[a1] + disToRoot[a2] - 2 * disToRoot[rmq_which[min_st(rmq_first[a1], rmq_first[a2])]]);

        }

    }

    return 0;
}

解法4:

#include <iostream>
#include <algorithm>
#include <cstring>
#include <string>
#include <cstdio>
#include <cmath>

using namespace std;

//31ms
//轉化爲RMQ問題_Segment_Tree

/**
*   注意線段樹需要4倍空間
*   注意線段樹需要4倍空間
*   注意線段樹需要4倍空間
*/

//在線算法

//孩子鏈表示法(鏈表)
//這裏的孩子不一定是子節點,可能還有一個父節點,但是可以用visited數組來區別
//隨便選取一個hand作爲根節點,就可以建成一棵樹

int hand[40005];//保存第x號房子的所有孩子節點鏈表的起始節點編號

struct Node//鏈表節點
{
    int distance;//權值
    int to;
    int next;//保存下一個Node的位置(並非指房子的編號,是節點的編號),即孩子組成的鏈表中的下一個節點的位置
};

Node nodes[40005 << 1];//需要兩倍空間

int pos;//pos爲nodes的當前可用的Node的位置編號
int disToRoot[40005];//代表到根節點的距離
// 結果 = disToRoot[x] + disToRoot[y] - 2 * disToRoot[LCA(x, y)]

//RMQ
int rmq_which[40005 << 1];//RMQ數組長度約爲節點數的兩倍(實際上是2n-1),儲存歐拉環遊經過的所有節點號
int rmq_deep[40005 << 1];//儲存環遊中節點的深度
int rmq_first[40005];//儲存循環中 x 號節點(房子)第一次出現的位置
int rmq_pos;

int visited[40005];//排除父節點

//Segment_Tree
struct SegNode
{
    int left;
    int right;
    int minPos;//儲存[left, right]區間內deep最小值所處的位置
};

//線段樹需要原基礎數組長度四倍的空間
SegNode segs[40005 << 3];//1開始

void addToTree(int x, int y, int z) {//表示爲x號房子添加一個子節點 y ,距離爲z
    //爲pos號節點寫入數據
    nodes[pos].to = y;
    nodes[pos].distance = z;
    nodes[pos].next = hand[x];
    hand[x] = pos;

    pos++;//當前可用的節點編號+1
}

/**
* rmq函數用歐拉環遊生成了rmq_which 和 rmq_deep 和 rmq_first 和 disToRoot 四個數組
*/
void rmq(int which, int deep) {
    visited[which] = 1;

    rmq_which[rmq_pos] = which;
    rmq_deep[rmq_pos] = deep;
    rmq_first[which] = rmq_pos;
    rmq_pos++;

    int childPos = hand[which];

    while (childPos) {
        if (visited[nodes[childPos].to]) {
            childPos = nodes[childPos].next;
            continue;
        }

        disToRoot[nodes[childPos].to] = disToRoot[which] + nodes[childPos].distance;

        rmq(nodes[childPos].to, deep + 1);

        rmq_which[rmq_pos] = which;
        rmq_deep[rmq_pos] = deep;
        rmq_pos++;

        childPos = nodes[childPos].next;

    }

}

//初始化segment數組
void build_seg(int spos, int left, int right) {
    // printf("spos -> %d\n", spos);
    segs[spos].left = left;
    segs[spos].right = right;

    if (left == right) {
        segs[spos].minPos = left;
        return;
    }

    build_seg(spos << 1, left, (left + right) / 2);
    build_seg((spos << 1) | 1, ((left + right) / 2) + 1, right);

    segs[spos].minPos = rmq_deep[segs[spos << 1].minPos] < rmq_deep[segs[(spos << 1) | 1].minPos] ? segs[spos << 1].minPos : segs[(spos << 1) | 1].minPos;

}

// 返回rmq_deep數組區間[x, y]之間的最小元素的位置
int min_seg(int pos, int x, int y) {
    if (x == segs[pos].left && y == segs[pos].right) {
        return segs[pos].minPos;
    } else if (y <= ((segs[pos].left + segs[pos].right) / 2)) {
        return min_seg(pos << 1, x, y);
    } else if (x > ((segs[pos].left + segs[pos].right) / 2)) {
        return min_seg((pos << 1) | 1, x, y);
    } else {
        int temp1, temp2;
        temp1 = min_seg(pos << 1, x, (segs[pos].left + segs[pos].right) / 2);
        temp2 = min_seg((pos << 1) | 1, ((segs[pos].left + segs[pos].right) / 2) + 1, y);

        return rmq_deep[temp1] < rmq_deep[temp2] ? temp1 : temp2;
    }
}

int main(int argc, char const *argv[])
{
    int T, n, m, a1, a2, a3, first = 1;
    scanf("%d", &T);
    while (T--) {
        scanf("%d%d", &n, &m);

        memset(hand, 0, sizeof(int) * (n + 1));
        memset(visited, 0, sizeof(int) * (n + 1));

        pos = 1;

        while (--n) {
            scanf("%d%d%d", &a1, &a2, &a3);

            addToTree(a1, a2, a3);
            addToTree(a2, a1, a3);

        }

        disToRoot[1] = 0;

        //在線算法(逐個回答問題)

        rmq_pos = 0;

        rmq(1, 1);

        // printf("rmq_pos -> %d\n", rmq_pos);

        build_seg(1, 0, rmq_pos - 1);

        if (first) {//好像沒有空行也能過
            first = 0;
        } else {
            printf("\n");
        }

        // for (int i = 0; i < rmq_pos; i++) {
        //  printf("%d ", rmq_which[i]);
        // }
        // printf("\n");
        // for (int i = 0; i < rmq_pos; i++) {
        //  printf("%d ", rmq_deep[i]);
        // }
        // printf("\n");
        // for (int i = 1; i <= 6; i++) {
        //  printf("%d ", rmq_first[i]);
        // }
        // printf("\n");

        while (m--) {

            scanf("%d%d", &a1, &a2);

            if (rmq_first[a1] < rmq_first[a2]) {

                printf("%d\n", disToRoot[a1] + disToRoot[a2] - 2 * disToRoot[rmq_which[min_seg(1, rmq_first[a1], rmq_first[a2])]]);
            } else {
                printf("%d\n", disToRoot[a1] + disToRoot[a2] - 2 * disToRoot[rmq_which[min_seg(1, rmq_first[a2], rmq_first[a1])]]);
            }

        }

    }

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