기본문제
https://www.acmicpc.net/problem/11438
https://www.acmicpc.net/problem/1761
'트리'에서 두 정점간의 공통 조상 중 가장 가까운 조상의 번호를 찾는 알고리즘이다. O(logn)
(0번노드가 첫 노드)
<O(n)>
기본 버전이다. O(n)
코드 이해하기가 굉장히 쉽다. 하지만 사용하진 않을 것. LCA 알고리즘이 크게 어떻게 돌아가는지 파악하기만 하면 됨.
int depth[MAX], parent[MAX];
void dfs(int curr) {
vst[curr] = true;
for (int next : adj[curr]) {
if (!vst[next]) {
depth[next] = depth[curr] + 1;
parent[next] = curr;
dfs(next);
}
}
}
int lca(int u, int v) {
if (depth[u] < depth[v]) swap(u, v);
//u,v 깊이 맞추기
while (depth[u] != depth[v])
u = parent[u];
//LCA까지 끌어올리기
while (u != v) {
u = parent[u];
v = parent[v];
}
return u;
}
<O(logn)>
위 기본버전을 dp로 최적화 한 것. 전처리하는데 O(n), lca수행시간은 O(logn)
각 정점의 depth를 활용해서 LCA를 구해낸다.
필요한 것 :
*depth[i] :
root ~ i 거리
*parent[i][j] :
i번 정점의 (1<<j)번째 조상.
parent[i][j+1] = parent[ parent[i][j] ][ j ] 를 만족한다. (1<<j+1 = 1<<j + 1<<j)
ex)i번정점의 2^3번째 조상은 i번정점의 2^2번째 조상의 2^2번째 조상이다.
1. dfs를 돌며 p,depth배열을 채워준다.
2.LCA함수를 짠다.
int depth[MAX], p[MAX][20], n;
vector<int> adj[MAX];
void dfs(int curr, int prv) {
//깊이 초기화
depth[curr] = depth[prv] + 1;
//조상 dp
p[curr][0] = prv;
for (int i = 1; (1 << i) < n; i++)
p[curr][i] = p[p[curr][i - 1]][i - 1];
//dfs
for (int next : adj[curr])
if (next != prv)
dfs(next, curr);
}
int lca(int u, int v) {
if (depth[u] < depth[v]) swap(u, v);
int log = 1;
for (log = 1; (1 << log) <= depth[u]; log++);
log--;
for (int i = log; i >= 0; i--)
if (depth[u] - (1 << i) >= depth[v])
u = p[u][i];
if (u == v) return u;
for (int i = log; i >= 0; i--) {
if (p[u][i] != 0 && p[u][i] != p[v][i]) {
u = p[u][i];
v = p[v][i];
}
}
return p[u][0];
}
u의 깊이가 v의 깊이 이하이면서 최대가 되도록 맞춰준 후,
u와 v가 같다면 v가 u의 조상인 경우로 바로 u를 반환하면 된다.
그 외의 경우, u와 v의 (1<<i)번째 조상이 존재하고, u,v,의 조상이 서로 다를때만 올려준다.
올릴수있을 때까지 올린 후에, u,v는 서로 같을 수 없다. p[u][0]가 바로 LCA이다.
<O(logn)>
위와 비슷하지만 depth대신에 isAncestor(u,v), u가 v의 조상인지 반환하는 함수를 이용한다.
1.dfs를 돌면서 p, tin(dfs들어간 순서), tout(dfs나온순서) 배열을 그때 그때 초기화 한다.
2.tin, tout 배열을 기반으로 isAncestor함수를 짠다.
//u가 v의 조상인지?
bool isAncestor(int u, int v) {
return (tin[u] <= tin[v] && tout[v] <= tout[u]);
}
3.LCA함수를 짠다.
int tin[MAX], tout[MAX], p[MAX][20], n;
vector<int> adj[MAX];
//p를 그때그때 초기화.
void dfs(int curr, int parent) {
tin[curr] = ++timer;
p[curr][0] = parent;
//n은 정점 수.
for (int i = 1; (1<<i) < n; i++)
p[curr][i] = p[p[curr][i - 1]][i - 1];
for (int next : adj[curr])
if (next != parent)
dfs(next, curr);
tout[curr] = ++timer;
}
//u가 v의 조상인지?
bool isAncestor(int u, int v) {
return (tin[u] <= tin[v] && tout[v] <= tout[u]);
}
int LCA(int u, int v) {
if (isAncestor(u, v)) return u;
if (isAncestor(v, u)) return v;
//u가 lca(u,v)의 바로밑에 있을 때 까지 2^i씩 위로 올라감
//단, p의 2^i번째 부모가 반드시 있어야함. i엔 그냥 한계치(19) 박아버려도 됨
for (int i = 19; i >= 0; i--)
if (p[u][i] != 0 && !isAncestor(p[u][i], v))
u = p[u][i];
return p[u][0];
}
두 방법 모두 숙지해 두자
'알고리즘 > 메모' 카테고리의 다른 글
pair 배열에서 이분탐색 stl 사용하기 (lower_bound 등) (0) | 2019.11.14 |
---|---|
치킨 맥너겟 이론 (chicken mcnugget theorem) (0) | 2019.11.03 |
행렬의 표현 (0) | 2019.09.27 |
KMP (0) | 2019.09.18 |
lazy propagation - 세그먼트 트리 확장 (0) | 2019.09.18 |