標題真長。。。
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;
}