UPDATE: 2019.05.13
· 修改RMQ做法的代码
CONTENT: LCA的RMQ算法、Tarjan算法、倍增算法
Update (2019.05.13)
- 修改RMQ做法的代码
BB (2019.05.13)
昨天GDCPC剩两个钟的时候和jjdl说你要是把I题A掉了,我就发pyq和说说通报表扬jjdl
不过蒟蒻觉得公号也应该通报表扬!jjdl niubility!!(破音)
BB
LCA是寒假集训的内容,当时一脸懵逼,现在好多了!
Introduction
LCA为最近公共祖先,有倍增、RMQ、Tarjan三种解法
- LCA
https://blog.csdn.net/my_sunshine26/article/details/72717112 - LCA —— 倍增
https://zhyack.github.io/posts/2015_12_22_LCA-Binary-Lifting-Note.html - LCA —— RMQ
https://www.cnblogs.com/549294286/p/3783423.html
关于对 倍增算法 的理解
倍增算法是个神奇的东西
首先用一次DFS,将所有点的深度找出来,以及所以点的父亲fa[u][0]
找出来
然后初始化fa数组,此时fa数组代表的是u节点往上走 2^j 步是谁,那么可以用DP推出fa数组,即fa[u][j] = fa[fa[u][j - 1]][j - 1]
最后是找LCA,对于给定的点u和v,首先尝试将其跳到同一深度,只需要往下枚举j,一直跳 2^j 步,整个过程满足dpt[v] > dpt[u]
即可,此时如果u和v是相同的,那么LCA就是u,如果不同,就两个节点接着一起跳 2^j 步,整个过程满足fa[u][j] != fa[v][j]
即可,最后他们的LCA是fa[u][0]
为什么这样做是正确的呢?对于第一个使u和v达到同一深度的过程,假设需要跳x步,我们知道任何一个数都可以用二进制数表示,假设其为11101011,那么往下枚举j的过程实际就是不断从高位往低位消除1的过程,如果该位为1。自然是能跳的,如果为0,则减去 2^j 会变成负数,也就是跳过头,具体体现在dpt[v] <= dpt[u]
,而对于第二个过程同理
蒟蒻感觉整个倍增算法充满了DP的味道,所以很多需要DP结合LCA的东西可以直接在倍增算法上面改
How far away ? - HDU - 2586
题意
给定一颗树,多次询问点u和v的距离
思路 —— 倍增做法
LCA模板题
u和v的距离也就是 d[u] + d[v] - 2*d[lca(u, v)]
,直接默认点1为根即可
#include <iostream>
#include <cstdio>
#include <stack>
#include <cstring>
#include <algorithm>
#include <cmath>
using namespace std;
const int N = 40000 + 5;
const int BASE = 16;
typedef long long ll;
struct edge{
int v, w, nxt;
};
edge e[N << 1];
int head[N], tot;
int fa[N][17], dpt[N], d[N];
inline void init(){
memset(head, -1, sizeof(head));
memset(fa, -1, sizeof(fa));
dpt[1] = 0, d[1] = 0;
tot = 0;
}
inline void addEdge(int u, int v, int w){
e[tot] = edge{v, w, head[u]};
head[u] = tot++;
}
void dfs(int u, int pre){
for(int i = head[u]; ~i; i = e[i].nxt){
int v = e[i].v;
if(v == pre) continue;
dpt[v] = d[u] + 1;
d[v] = d[u] + e[i].w;
fa[v][0] = u;
dfs(v, u);
}
}
void getFa(int n){
for(int j = 1; j <= BASE; j++){
for(int i = 1; i <= n; i++){
if(fa[i][j - 1] == -1) continue;
fa[i][j] = fa[fa[i][j - 1]][j - 1];
}
}
}
int getLca(int u, int v){
if(dpt[u] > dpt[v]) swap(u, v);
for(int j = BASE; j >= 0; j--){
if(fa[v][j] == -1 || dpt[fa[v][j]] < dpt[u]) continue;
v = fa[v][j];
}
if(u == v) return u;
for(int j = BASE; j >= 0; j--){
if(fa[u][j] == -1 || fa[v][j] == -1 || fa[u][j] == fa[v][j]) continue;
u = fa[u][j], v = fa[v][j];
}
return fa[u][0];
}
int main(){
int t;
scanf("%d", &t);
while(t--){
init();
int n, m;
scanf("%d%d", &n, &m);
for(int i = 1; i <= n - 1; i++){
int u, v, w;
scanf("%d%d%d", &u, &v, &w);
addEdge(u, v, w);
addEdge(v, u, w);
}
dfs(1, -1);
getFa(n);
while(m--){
int u, v;
scanf("%d%d", &u, &v);
printf("%d\n", d[u] + d[v] - 2*d[getLca(u, v)]);
}
}
}
思路 —— Tarjan做法
#include <iostream>
#include <cstdio>
#include <map>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 40000 + 5;
const int M = 200 + 5;
typedef long long ll;
struct edge{
int v, w, nxt;
};
edge e[N << 1];
int head[N], tot, d[N], used[N];
int ft[N];
edge qe[M << 1];
int qhead[N], qtot;
int pa[N];
inline void init(){
for(int i = 1; i < N; i++) ft[i] = i;
memset(head, -1, sizeof(head));
memset(qhead, -1, sizeof(qhead));
memset(used, false, sizeof(used));
d[1] = 0;
tot = 0;
qtot = 0;
}
inline void addEdge(int u, int v, int w){
e[tot] = edge{v, w, head[u]};
head[u] = tot++;
}
inline void addQ(int u, int v, int i){
qe[qtot] = edge{v, i, qhead[u]};
qhead[u] = qtot++;
}
int find(int x){ return ft[x] == x ? x : ft[x] = find(ft[x]); }
void merge(int x, int y){
int p = find(x), q = find(y);
if(p != q) ft[q] = p;
}
void tarjan(int u, int pre, int q){
for(int i = head[u]; ~i; i = e[i].nxt){
int v = e[i].v;
if(v == pre) continue;
d[v] = d[u] + e[i].w;
tarjan(v, u, q);
merge(u, v);
used[v] = true;
}
for(int i = qhead[u]; ~i; i = qe[i].nxt){
int v = qe[i].v;
if(used[v]) pa[qe[i].w] = find(v);
}
}
int main(){
int t;
scanf("%d", &t);
while(t--){
init();
int n, q;
scanf("%d%d", &n, &q);
for(int i = 1; i <= n - 1; i++){
int u, v, w;
scanf("%d%d%d", &u, &v, &w);
addEdge(u, v, w);
addEdge(v, u, w);
}
for(int i = 0; i < q; i++){
int u, v;
scanf("%d%d", &u, &v);
addQ(u, v, i);
addQ(v, u, i);
}
tarjan(1, -1, q);
for(int i = 0; i < q; i++){
printf("%d\n", d[qe[2*i].v] + d[qe[2*i + 1].v] - 2*d[pa[i]]);
}
}
}
思路 —— RMQ做法
#include <iostream>
#include <cstdio>
#include <stack>
#include <cstring>
#include <algorithm>
#include <cmath>
using namespace std;
const int N = 40000 + 5;
typedef long long ll;
struct edge{
int v, w, nxt;
};
edge e[N << 1];
int head[N], tot;
int dp[N << 1][21], mp[N << 1], pos[N], d[N], dfn, totDp;
inline void init(){
memset(head, -1, sizeof(head));
d[1] = tot = dfn = totDp = 0;
}
inline void addEdge(int u, int v, int w){
e[tot] = edge{v, w, head[u]};
head[u] = tot++;
}
void dfs(int u, int pre){
int curDfn = ++dfn;
dp[++totDp][0] = curDfn;
mp[curDfn] = u;
pos[u] = totDp;
for(int i = head[u]; ~i; i = e[i].nxt){
int v = e[i].v;
if(v == pre) {
continue;
}
d[v] = d[u] + e[i].w;
dfs(v, u);
dp[++totDp][0] = curDfn;
}
}
void rmqInit(){
for(int j = 1; (1 << j) <= totDp; j++){
for(int i = 1; i + (1 << j) - 1 <= totDp; i++){
dp[i][j] = min(dp[i][j - 1], dp[i + (1 << (j - 1))][j - 1]);
}
}
}
int getLca(int u, int v){
int l = pos[u], r = pos[v];
if(l > r) {
swap(l, r);
}
int j = (int)log2(r - l + 1);
return mp[min(dp[l][j], dp[r - (1 << j) + 1][j])];
}
int main(){
int t;
scanf("%d", &t);
while(t--){
init();
int n, m;
scanf("%d%d", &n, &m);
for(int i = 1; i <= n - 1; i++){
int u, v, w;
scanf("%d%d%d", &u, &v, &w);
addEdge(u, v, w);
addEdge(v, u, w);
}
dfs(1, -1);
rmqInit();
while(m--){
int u, v;
scanf("%d%d", &u, &v);
printf("%d\n", d[u] + d[v] - 2*d[getLca(u, v)]);
}
}
}
Network - HDU - 3078
题意
给定一棵数,求u到v的路径中第k大权值,或者修改点u的权值
思路 —— 暴力
没想打暴力可取 = =||
注意,这个第k大是真的第k大,不是第k小
#include <iostream>
#include <cstdio>
#include <vector>
#include <cstring>
#include <algorithm>
#include <functional>
using namespace std;
const int N = 80000 + 5;
const int BASE = 16;
typedef long long ll;
struct edge{
int v, nxt;
};
edge e[N << 1];
int head[N], tot;
int fa[N][17], dpt[N], val[N];
inline void init(){
memset(head, -1, sizeof(head));
memset(fa, -1, sizeof(fa));
dpt[1] = 0;
tot = 0;
}
inline void addEdge(int u, int v){
e[tot] = edge{v, head[u]};
head[u] = tot++;
}
void dfs(int u, int pre){
for(int i = head[u]; ~i; i = e[i].nxt){
int v = e[i].v;
if(v == pre) continue;
dpt[v] = dpt[u] + 1;
fa[v][0] = u;
dfs(v, u);
}
}
void getFa(int n){
for(int j = 1; j <= BASE; j++){
for(int i = 1; i <= n; i++){
if(fa[i][j - 1] == -1) continue;
fa[i][j] = fa[fa[i][j - 1]][j - 1];
}
}
}
int getLca(int u, int v){
if(dpt[u] > dpt[v]) swap(u, v);
for(int j = BASE; j >= 0; j--){
if(fa[v][j] == -1 || dpt[fa[v][j]] < dpt[u]) continue;
v = fa[v][j];
}
if(u == v) return u;
for(int j = BASE; j >= 0; j--){
if(fa[u][j] == -1 || fa[v][j] == -1 || fa[u][j] == fa[v][j]) continue;
u = fa[u][j], v = fa[v][j];
}
return fa[u][0];
}
int main(){
int n, m;
while(~scanf("%d%d", &n, &m)){
init();
for(int i = 1; i <= n; i++){
scanf("%d", &val[i]);
}
for(int i = 1; i <= n - 1; i++){
int u, v;
scanf("%d%d", &u, &v);
addEdge(u, v);
addEdge(v, u);
}
dfs(1, -1);
getFa(n);
while(m--){
int k, u, v;
scanf("%d%d%d", &k, &u, &v);
if(!k){
val[u] = v;
}else{
k--;
vector<int> vec;
int ulca = getLca(u, v);
for(int tmp = u; tmp != ulca; tmp = fa[tmp][0]) vec.push_back(val[tmp]);
for(int tmp = v; tmp != ulca; tmp = fa[tmp][0]) vec.push_back(val[tmp]);
vec.push_back(val[ulca]);
sort(vec.begin(), vec.end(), greater<int>());
if(k >= vec.size()) puts("invalid request!");
else printf("%d\n", vec[k]);
}
}
}
}
Bond - UVA - 11354
题意
给定一幅图,求u到v的边中的最大权值,要求该权值最小
思路 —— MST + LCA倍增
首先由于要求最大权值最小,所以跑最小生成树
然后用LCA倍增,维护dp[i][j]状态下的权值最下值
查询时再不断更新答案即可
#include <iostream>
#include <cstdio>
#include <vector>
#include <cstring>
#include <algorithm>
#include <functional>
using namespace std;
const int N = 5e4 + 5;
const int M = 1e5 + 5;
const int BASE = 16;
typedef long long ll;
struct kedge{
int u, v, w;
bool operator < (const kedge& b) const{ return w < b.w; }
}in[M];
struct edge{
int v, w, nxt;
};
edge e[M << 1];
int head[N], tot;
int fa[N][17], dp[N][17], dpt[N], val[N];
int ft[N];
inline void init(){
for(int i = 0; i < N; i++) ft[i] = i;
memset(head, -1, sizeof(head));
memset(fa, -1, sizeof(fa));
dpt[1] = 0;
tot = 0;
}
int find(int x){ return ft[x] == x ? x : ft[x] = find(ft[x]); }
void merge(int x, int y){
int p = find(x), q = find(y);
if(p != q) ft[q] = p;
}
inline void addEdge(int u, int v, int w){
e[tot] = edge{v, w, head[u]};
head[u] = tot++;
}
void Kruskal(int m){
sort(in, in + m);
for(int i = 0; i < m; i++){
if(find(in[i].u) == find(in[i].v)) continue;
merge(in[i].u, in[i].v);
addEdge(in[i].u, in[i].v, in[i].w);
addEdge(in[i].v, in[i].u, in[i].w);
}
}
void dfs(int u, int pre){
for(int i = head[u]; ~i; i = e[i].nxt){
int v = e[i].v;
if(v == pre) continue;
dpt[v] = dpt[u] + 1;
fa[v][0] = u;
dp[v][0] = e[i].w;
dfs(v, u);
}
}
void getFa(int n){
for(int j = 1; j <= BASE; j++){
for(int i = 1; i <= n; i++){
if(fa[i][j - 1] == -1) continue;
fa[i][j] = fa[fa[i][j - 1]][j - 1];
dp[i][j] = max(dp[i][j - 1], dp[fa[i][j - 1]][j - 1]);
}
}
}
int getLca(int u, int v){
int ans = 0;
if(dpt[u] > dpt[v]) swap(u, v);
for(int j = BASE; j >= 0; j--){
if(fa[v][j] == -1 || dpt[fa[v][j]] < dpt[u]) continue;
ans = max(ans, dp[v][j]);
v = fa[v][j];
}
if(u == v){
//ans = max(ans, dp[v][0]);
return ans;
}
for(int j = BASE; j >= 0; j--){
if(fa[u][j] == -1 || fa[v][j] == -1 || fa[u][j] == fa[v][j]) continue;
ans = max(ans, dp[v][j]);
ans = max(ans, dp[u][j]);
u = fa[u][j], v = fa[v][j];
}
ans = max(ans, dp[v][0]);
ans = max(ans, dp[u][0]);
//ans = max(ans, dp[fa[u][0]][0]);
return ans;
}
int main(){
bool first = true;
int n, m;
while(~scanf("%d%d", &n, &m)){
if(!first) puts("");
else first = false;
init();
for(int i = 0; i < m; i++){
scanf("%d%d%d", &in[i].u, &in[i].v, &in[i].w);
}
Kruskal(m);
dfs(1, -1);
getFa(n);
int q;
scanf("%d", &q);
while(q--){
int u, v;
scanf("%d%d", &u, &v);
printf("%d\n", getLca(u, v));
}
}
}
Duff in the Army - CodeForces - 587C
题意
给定一棵树,树上的点有一些权值(可能不止1个,也可能没有),现问从u到v路径中的前a小的数是什么(a <= 10)
思路 —— LCA倍增 + 归并排序思想
用LCA倍增维护每个点存在的值,因为是单调递增的,所以用LCA倍增维护时可以用归并排序合并维护,查询的时候也是合并更新即可
#include <iostream>
#include <cstdio>
#include <vector>
#include <cstring>
#include <algorithm>
#include <functional>
using namespace std;
const int N = 100000 + 5;
const int BASE = 17;
typedef long long ll;
struct edge{
int v, nxt;
};
edge e[N << 1];
int head[N], tot;
int fa[N][18], dpt[N];
vector<int> vec[N][21];
inline void init(){
for(int i = 0; i < N; i++){
for(int j = 0; j < 21; j++){
vec[i][j].clear();
}
}
memset(head, -1, sizeof(head));
memset(fa, -1, sizeof(fa));
dpt[1] = 0;
tot = 0;
}
inline void addEdge(int u, int v){
e[tot] = edge{v, head[u]};
head[u] = tot++;
}
void dfs(int u, int pre){
for(int i = head[u]; ~i; i = e[i].nxt){
int v = e[i].v;
if(v == pre) continue;
dpt[v] = dpt[u] + 1;
fa[v][0] = u;
dfs(v, u);
}
}
void getFa(int n){
for(int j = 1; j <= BASE; j++){
for(int i = 1; i <= n; i++){
if(fa[i][j - 1] == -1) continue;
fa[i][j] = fa[fa[i][j - 1]][j - 1];
int p = 0, q = 0;
vector<int>& res = vec[i][j];
vector<int>& t1 = vec[i][j - 1];
vector<int>& t2 = vec[fa[i][j - 1]][j - 1];
while((p < t1.size() || q < t2.size()) && res.size() < 10){
if(p == t1.size()) res.push_back(t2[q++]);
else if(q == t2.size()) res.push_back(t1[p++]);
else if(t1[p] < t2[q]) res.push_back(t1[p++]);
else res.push_back(t2[q++]);
}
}
}
}
void mergeVec(vector<int>& ret, vector<int>& src){
int p = 0, q = 0;
vector<int> res;
vector<int>& t1 = ret;
vector<int>& t2 = src;
while((p < t1.size() || q < t2.size()) && res.size() < 10){
if(p == t1.size()) res.push_back(t2[q++]);
else if(q == t2.size()) res.push_back(t1[p++]);
else if(t1[p] < t2[q]) res.push_back(t1[p++]);
else res.push_back(t2[q++]);
}
ret.clear();
for(int i = 0; i < res.size(); i++){
ret.push_back(res[i]);
}
}
vector<int> getLca(int u, int v){
vector<int> ret;
if(dpt[u] > dpt[v]) swap(u, v);
for(int j = BASE; j >= 0; j--){
if(fa[v][j] == -1 || dpt[fa[v][j]] < dpt[u]) continue;
mergeVec(ret, vec[v][j]);
v = fa[v][j];
}
if(u == v){
mergeVec(ret, vec[u][0]);
return ret;
}
for(int j = BASE; j >= 0; j--){
if(fa[u][j] == -1 || fa[v][j] == -1 || fa[u][j] == fa[v][j]) continue;
mergeVec(ret, vec[u][j]);
mergeVec(ret, vec[v][j]);
u = fa[u][j], v = fa[v][j];
}
mergeVec(ret, vec[u][0]);
mergeVec(ret, vec[v][0]);
mergeVec(ret, vec[fa[u][0]][0]);
return ret;
}
int main(){
int n, m, q;
while(~scanf("%d%d%d", &n, &m, &q)){
init();
for(int i = 1; i <= n - 1; i++){
int u, v;
scanf("%d%d", &u, &v);
addEdge(u, v);
addEdge(v, u);
}
for(int i = 1; i <= m; i++){
int u;
scanf("%d", &u);
vec[u][0].push_back(i);
}
dfs(1, -1);
getFa(n);
while(q--){
int u, v, mind;
scanf("%d%d%d", &u, &v, &mind);
vector<int> ans = getLca(u, v);
printf("%d", min((int)ans.size(), mind));
for(int i = 0; i < min((int)ans.size(), mind); i++){
printf(" %d", ans[i]);
}
puts("");
}
}
}