倍增

当我们想要知道从最快地从A走到B时,朴素的想法是找出任何一个点走任意步会到达的地方,但是这样太耗内存。

但是实际上可以只记录走1,2,4,8,16步能到达的地方

A出发:若跳8个格子(超过B了,放弃)

                  若跳4个格子(超过B了,放弃)

                  若跳2个格子(没超过B,跳吧)

                  若跳1个格子(没超过B,跳吧)

        从B出发:…………

        多么轻松的事情,只要一本很薄的小抄就可以了,最关键的是:它绝对不会连着跳两步都是跳相同的格子数,因为如果跳两次2个格子都是可行的话,那么它干嘛不跳4个格子捏?

A出发跳1步到1(记录下来)

        从1出发跳1步到2(记录下来)

        …………(跳1步的记录完毕)

        从A出发跳2步?就是从A出发跳1步再跳1步的到的地方,翻看小抄,直接誊写从1出发跳1步会到的2这个格子作为A2步会到的格子。

        从1出发跳2步?跟刚才一样,直接誊写。

        …………(跳2步的记录完毕)

        从A出发跳4步?你还真去跳4步?不,它也就是等于从A出发跳2步到的2号点再跳2步会到的格子,那么我们直接誊写2号格子跳2步会到的格子就可以了。



以下转自:http://jiayuzun.coding.me/2016/08/05/bz-template/

算法理论

  • 朴素算法:记录下每个节点的父亲,使节点u,v一步一步地向上找父亲,直到找到相同的“祖先”,即是
    所求的答案,时间复杂度O(n)
  • 优化算法(倍增法):利用二进制的思想,想办法使一步一步向上搜索变成以2^k的向上跳。所以
    定义一个f[][]数组,使f[j][i]表示节点i的2^j倍祖先。

算法实现

1.预处理出所有节点的深度和父节点

* BFS防止爆栈 无法处理孩子个数
* DFS可能会爆栈 可以处理孩子个数 使用时建议扩栈

2.处理各节点的所有祖先节点

3.将所查询的两点上升到同一高度

* 找到祖先(以2^k的高度向上找)
* 未找到祖先,同时上升高度至找到公共祖先

附加代码

定义及初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#pragma comment(linker, "/STACK:10240000000,10240000000")//扩栈,要用c++交,用g++交并没有什么卵用。。。
const int N = 100005;
int n , m , pre[N] , rt[N], mcnt;//pre 邻接表数组 rt 求根节点 mcnt 邻接表下标变量
bool vis[N];
struct edge
{
int x , next;
} e[N << 1]; //邻接表
int dep[N] , f[17][N] , Lev , s[N];
//dep[]储存深度 1<<16 < N f[j][i] 表示i的第2^j个祖先 s[]孩子个数
void init()
{
memset(pre , -1 , sizeof(pre));
memset(rt, 0, sizeof(rt));
mcnt = 0;
}

算法函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
void addedge(int x, int y)//邻接表加边函数
{
e[mcnt].x = y,
e[mcnt].next = pre[x],
pre[x] = mcnt ++;
}
void dfs(int x , int fa)///也可以用bfs,但bfs不能统计节点孩子个数
{
dep[x] = dep[fa] + 1;
f[0][x] = fa , s[x] = 1;
for (int i = pre[x] ; i!=-1 ; i = e[i].next)
{
int y = e[i].x;
if (y != fa)
{
dfs(y , x);
s[x] += s[y];///节点x的孩子个数
}
}
}
// dfs处理后,要进一步处理得到节点的所有祖先
// for (j = 1 ; 1 << j < n ; ++ j)
// for (i = 1 ; i <= n ; ++ i)
// {
// f[j][i] = f[j - 1][f[j - 1][i]];
// }
// Lev = j - 1;
void bfs(int rt)///不需要求孩子个数,同时防止暴栈
{
queue<int> q;
q.push(rt);
f[0][rt] = 0, dep[rt] = 1, vis[rt] = 1;
while (!q.empty())
{
int fa = q.front();
q.pop();
for (int i = pre[fa] ; ~i ; i = e[i].next)
{
int x = e[i].x;
if (!vis[x])
{
dep[x] = dep[fa] + 1;
f[0][x] = fa , vis[x] = 1;
q.push(x);
}
}
}
}
int LCA(int x , int y)
{
if (dep[x] > dep[y])
{
swap(x , y);
}
for (int i = Lev ; i >= 0 ; -- i)///找y的第dep[y] - dep[x]个祖先
if (dep[y] - dep[x] >> i & 1)//dep[y]-dep[x]刚好比2的i次方大时
{
y = f[i][y];
}
if (x == y)
{
return y;
}
for (int i = Lev ; i >= 0 ; -- i)//同一高度后开始找祖先
if (f[i][x] != f[i][y])//不停的上次2的i次方,直到i==0
{
x = f[i][x] , y = f[i][y];
}
return f[0][x];
}
int get_kth_anc(int x , int k) ///找x的第k个祖先
{
for (int i = 0 ; i <= Lev ; ++ i)
if (k >> i & 1)
{
x = f[i][x];
}
return x;
}

posted @ 2018-06-22 21:31  wyboooo  阅读(182)  评论(0编辑  收藏  举报