LCA
倍增法求最近公共祖先
首先对于每个结点先进行dfs预处理它的深度,再记录下它们往父亲方向走2的0次,1次...k次步所到达的结点。在这里2的k次大于整棵树的最大深度。
预处理完后,需要查询两个点u,v的LCA时,先将u,v中深度较大的利用预处理的数组走到和另一个结点相同深度,操作次数不会超过log2|depth(u)-depth(v)|
接下来从k开始往下枚举,如果u和v往上走2的i次后不同那么它们一起往上走那么多步
预处理 O(nlogn)查询O(logn)
不仅如此我们可以动态地给树增加一些叶子结点,在预处理时还可以记录下这段路径权值最大值,最小值或权值和之类的信息。
Luogu P3379 https://www.luogu.com.cn/problem/P3379
#include<iostream> #include<cstdio> #include<string> #include<algorithm> #include<queue> #include<set> #include<map> const double PI = acos(-1.0); typedef long long ll; using namespace std; struct Edge { int t, next; }e[500010<<1]; //开两倍 int head[500010], tot; void add_edge(int x, int y) { e[++tot].t = y; e[tot].next = head[x]; head[x] = tot; } int depth[500010], fa[500001][22], lg[500001]; void dfs(int now, int fath) { //now表示当前节点,fath表示其父亲节点 fa[now][0] = fath; depth[now] = depth[fath] + 1; for (int i = 1; i <= lg[depth[now]]; i++) { fa[now][i] = fa[fa[now][i - 1]][i - 1]; //算法核心 } for (int i = head[now]; i; i = e[i].next) { if (e[i].t != fath) dfs(e[i].t, now); } } int LCA(int x, int y) { if (depth[x] < depth[y]) swap(x, y); while (depth[x] > depth[y]) x = fa[x][lg[depth[x] - depth[y]] - 1]; //先跳到同一层 if (x == y) return x; //如果x==y那LCA一定就是x for (int k = lg[depth[x]] - 1; k >= 0; k--) { //不断向上跳 if (fa[x][k] != fa[y][k]) { //因为要跳到LCA的下一层所以他们肯定不相等,不相等就跳过去 x = fa[x][k]; y = fa[y][k]; } } return fa[x][0]; } int main() { int n, m, s; scanf("%d%d%d", &n, &m, &s); for (int i = 1; i <= n - 1; i++) { int x, y; scanf("%d%d", &x, &y); add_edge(x, y); add_edge(y, x); } for (int i = 1; i <= n; i++) { //预先算出log2(i)+1的值,用的时候可以直接调用 lg[i] = lg[i - 1] + (1 << lg[i - 1] == i); } dfs(s, 0); for (int i = 1; i <= m; i++) { int x, y; scanf("%d%d", &x, &y); printf("%d\n", LCA(x, y)); } return 0; }
邻接表版本
#include<iostream> #include<cstdio> #include<string> #include<algorithm> #include<queue> #include<set> #include<map> const double PI = acos(-1.0); typedef long long ll; using namespace std; const int maxn = 500001; int depth[maxn], fa[maxn][22]; int lg[maxn]; vector<int> e[maxn]; void add_edge(int x, int y) { e[x].push_back(y); } void dfs(int u, int last) { //last是u的父亲结点 depth[u] = depth[last] + 1; fa[u][0] = last; for (int i = 1; i <=lg[depth[u]]; i++) { fa[u][i] = fa[fa[u][i - 1]][i - 1]; } for (int i = 0; i < e[u].size(); i++) { if (e[u][i] != last) dfs(e[u][i], u); } } int lca(int x, int y) { if (depth[x] < depth[y]) swap(x, y); while (depth[x] > depth[y]) { x = fa[x][lg[depth[x]-depth[y]]-1]; } if (x == y) return x; for (int k = lg[depth[x]]-1; k >= 0; k--) { if (fa[x][k] != fa[y][k]) x = fa[x][k], y = fa[y][k]; //printf("%d\n", fa[x][0]); } return fa[x][0]; } int main() { int n, m, s, x, y; scanf("%d%d%d", &n, &m, &s); for (int i = 1; i <= n; i++) { lg[i] = lg[i - 1] + (1 << lg[i - 1] == i); } for (int i = 1; i <= n - 1; i++) { scanf("%d%d", &x, &y); add_edge(x, y); add_edge(y, x); } dfs(s, 0); for (int i = 1; i <= m; i++) { scanf("%d%d", &x, &y); printf("%d\n", lca(x, y)); } return 0; }
POJ 1330 (邻接表存储)
#include<iostream> #include<cstdio> #include<string> #include<algorithm> #include<queue> #include<set> #include<map> const double PI = acos(-1.0); typedef long long ll; using namespace std; int fa[10005][16], depth[10005], d[10005]; vector<int> e[10005]; void dfs(int u) { for (int i = 1; i <= 15; i++) { fa[u][i] = fa[fa[u][i - 1]][i - 1]; } for (int i = 0; i < e[u].size(); i++) { int v = e[u][i]; depth[v] = depth[u] + 1; fa[v][0] = u; dfs(v); } } int lca(int u, int v) { if (depth[u] > depth[v]) { swap(u, v); } int t = depth[v] - depth[u]; for (int i = 15; i >= 0; i--) { if (1 << i & t) v = fa[v][i]; } if (u == v) return u; for (int i = 15; i >= 0; i--) { if (fa[u][i] != fa[v][i]) u = fa[u][i], v = fa[v][i]; } return fa[u][0]; } int main() { int n, T; scanf("%d", &T); while (T--) { memset(depth, 0, sizeof depth); memset(fa, 0, sizeof fa); memset(d, 0, sizeof d); scanf("%d", &n); int u, v; for (int i = 1; i < n; i++) { scanf("%d%d", &u, &v); e[u].push_back(v); d[v]++; } scanf("%d%d", &u, &v); int rt = 0; for (int i = 1; i <= n; i++) if (!d[i]) rt = i; dfs(rt); printf("%d\n", lca(u, v)); for (int i = 1; i <= n; i++) { e[i].clear(); } } return 0; }
CodeForces - 697C Lorenzo Von Matterhorn
不需要求出LCA 数的移动(二叉树编号)
#include<iostream> #include<map> using namespace std; typedef unsigned long long ll; map<ll,ll> mp; int main(){ int q; int f; ll l,r,w; scanf("%d",&q); for(int i=0;i<q;i++){ scanf("%d",&f); if(f==1) { scanf("%lld%lld%lld",&l,&r,&w); while(l!=r){ if(l>r) mp[l]+=w,l/=2; else mp[r]+=w,r/=2; } } else { scanf("%lld%lld",&l,&r); ll ans=0; while(l!=r){ if(l>r) ans+=mp[l],l/=2; else ans+=mp[r],r/=2; } printf("%lld\n",ans); } } return 0; }
倍增应用
From OI wiki
#include<iostream> #include<cstdio> #include<string> #include<algorithm> #include<queue> #include<set> #include<map> #include<cmath> const double PI = acos(-1.0); #define INF 0x3f3f3f3f typedef long long ll; using namespace std; const int mod = 1000000007; int modadd(int a, int b) { if (a + b >= mod) return a + b - mod; //加快模运算 return a + b; } int vi[1000005]; int go[75][1000005]; //go[i][x]表示第x个点跳2^i步后的终点 int sum[75][1000005]; //sum[i][x]表示第x个点跳2^i步之后能获得的点权和 int main() { int n, k; scanf("%d%d", &n, &k); for (int i = 1; i <= n; i++) { scanf("%d", &vi[i]); } for (int i = 1; i <= n; i++) { go[0][i] = (i + k) % n + 1; sum[0][i] = vi[i]; } int logn = 31 - __builtin_clz(n); //快捷的取2对数方法 for (int i = 1; i <= logn; i++) { for (int j = 1; j <= n; j++) { go[i][j] = go[i - 1][go[i - 1][j]]; sum[i][j] = modadd(sum[i - 1][j], sum[i - 1][sum[i - 1][j]]); } } ll m; scanf("%lld", &m); int ans = 0; int curx = 1; for (int i = 0; m; i++) { if (m & (1 << i)) { ans = modadd(ans, sum[i][curx]); curx = go[i][curx]; m ^= 1ll << i; //将第i位置零 //1ll是64位的1 } } printf("%d", ans); return 0; }