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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章