BOJ 백준 30092 슥삭슥삭 나무자르기
문제 : https://www.acmicpc.net/problem/30092
[ 문제 요약 ]
- 노드 N 개와, 간선이 N-1 개인 트리 정보가 주어지며, 쿼리에 답합니다.
- 쿼리는 A, B, C, D가 주어지며, A에서 B로 가는 최단 경로의 간선을 삭제했을 때, C에서 D로 갈 수 있다면 YES 출력, 없다면 NO 출력입니다.
[ 테스트 케이스 설명 ]
8 4 // 노드 수(2<=100,000), 쿼리수(1<=300,000)
1 2 // 연결된 두 노드번호로 양방향 연결
1 3
3 4
3 5
3 6
4 7
4 8
6 7 2 3 // A,B,C,D가 주어지며 A->B로 가는 최단 경로에 모든 간선제거 후, C->D로 가는 경로가 있다면 YES, 없으면 NO출력
6 7 7 8
4 6 1 5
4 7 4 8
//답
YES
NO
YES
YES
[ 문제 해설 ]
이 문제는 기본적인 HLD와 Lazy Propagation 문제입니다.
https://www.acmicpc.net/problem/16453 이 문제와 비슷하지만, 쿼리수가 더 많습니다.
최댓값 세그먼트 트리를 만들고, 간선이 연결되어 있는 것은 값이 0, 연결되어 있지 않다면 1을 표현하면 노드 간 이동 시 쉽게 연결 유무를 확인할 수 있습니다.
처음 A-B 노드를 지날 때, 해당 구간에 전부 1을 더해주고, 나중에 C-D 구간 지나면서 구간의 MAX 값을 찾아오면, 1이면 연결되어 있지 않은 것, 0이면 연결되어 있는 것으로 판단합니다.
그리고 A-B 노드를 다시 지나면서, 1을 더했던 것을 0으로 만들어 주기 위해 -1을 다시 더해줍니다.
여기서 중요한 것은, 세그먼트 트리의 인덱스마다 저장되는 값은 간선의 유무를 저장하는 것입니다. 예를 들어 A, B 노드가 있을 때 두 노드의 세그먼트 트리에 대응하는 인덱스가 ㄴ1,2라 하면, 간선의 값을 2에 저장합니다. 그래야 나중에 쿼리 시 정확한 간선을 탐색할 수 있습니다.
단순히 각 노드의 가중치를 저장하는 것이면 1,2에 각각의 가중치를 저장할 테지만, 간선 정보기 때문에 두 노드 중 자식 노드에 가중치를 저장해야 일관적으로 값을 구할 수 있습니다.
HLD 알고리즘의 기본 설명은 아래 링크에 나와있습니다.
https://kyjdummy.tistory.com/21
BOJ 백준 13510 트리와 쿼리 1
문제 : https://www.acmicpc.net/problem/13510[ 문제 요약 ]N 개의 노드와 N - 1개의 간선이 주어지며 2가지 명령어가 임의로 주어집니다.1 i c : i 번 간선의 비용을 c로 변경2 u v : u에서 v로 가는 단순 경로에
kyjdummy.tistory.com
[ 정답 코드 ]
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.StringTokenizer;
class Main{
static int N, Q;
static int idx; // 노드번호 -> 세그먼트 트리 인덱스 변환할 변수
static int[] tree; // 세그먼트 트리
static int[] lazy; // lazy propagation을 위한 배열
static int[] treeIdx; // 노드번호 -> 세그먼트 트리 인덱스 변환 정보를 담은 배열
static int[] chainHead; // 체인의 head값
static int[] chainLevel; // 체인의 level
static int[] chainParent; // 이전 체인으로 점프를 위한 이전 체인의 도착노드 값
static ArrayList<Integer>[] adNode;
public static void main(String[] args)throws Exception{
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
StringTokenizer st = new StringTokenizer(br.readLine());
StringBuilder sb = new StringBuilder();
N = Integer.parseInt(st.nextToken());
Q = Integer.parseInt(st.nextToken());
treeIdx = new int[N + 1];
chainHead = new int[N + 1];
chainLevel = new int[N + 1];
chainParent = new int[N + 1];
tree = new int[N * 4];
lazy = new int[N * 4];
adNode = new ArrayList[N + 1];
for(int i=0; i<=N; i++)
adNode[i] = new ArrayList<>();
for(int i=1; i<N; i++)
{
st = new StringTokenizer(br.readLine());
int a = Integer.parseInt(st.nextToken());
int b = Integer.parseInt(st.nextToken());
adNode[a].add(b);
adNode[b].add(a);
}
// 이 함수에서 자식 노드의 크기를 확인하여 인접노드에 첫번째에 가장 무거운 노드를 오게 한다.
setSize(1, 0, new int[N + 1]);
// 1번 노드의 헤드는 자기자신
chainHead[1] = 1;
// HLD에 필요한 값 세팅
setHLD(1, 0, 1);
while(Q-->0)
{
st = new StringTokenizer(br.readLine());
int a = Integer.parseInt(st.nextToken());
int b = Integer.parseInt(st.nextToken());
int c = Integer.parseInt(st.nextToken());
int d = Integer.parseInt(st.nextToken());
rangeUpdate(a, b, 1); // a-b 노드 구간을 1로 업데이트하여 노드가 끊어짐을 표현
sb.append(isConnect(c,d) ? "YES" : "NO").append('\n');
rangeUpdate(a, b, -1); // a-b 노드 구간을 다시 -1을 더해 노드 연결을 복구
}
System.out.print(sb);
}
static boolean isConnect(int n1, int n2) {
// 체인 헤드가 같아질 때 까지 레벨을 올리면서 query결과가 true면 false를 반환해서 연결이 끊어짐을 알린다.
while(chainHead[n1] != chainHead[n2])
{
if(chainLevel[n1] > chainLevel[n2])
{
int tmp = n1;
n1 = n2;
n2 = tmp;
}
// 쿼리 결과가 1이면 끊어진 것
if(1 == query(1, 1, N, treeIdx[chainHead[n2]], treeIdx[n2]))
return false;
n2 = chainParent[n2];
}
if(treeIdx[n1] > treeIdx[n2])
{
int tmp = n1;
n1 = n2;
n2 = tmp;
}
// 쿼리 결과가 1이 아니여야 연결되어 있는 것
return 1 != query(1, 1, N, treeIdx[n1] + 1, treeIdx[n2]);
}
static void rangeUpdate(int n1, int n2, int value) {
// 체인 헤드가 같아질 때 까지 레벨을 올린다.
while(chainHead[n1] != chainHead[n2])
{
if(chainLevel[n1] > chainLevel[n2])
{
int tmp = n1;
n1 = n2;
n2 = tmp;
}
update(1, 1, N, treeIdx[chainHead[n2]], treeIdx[n2], value);
n2 = chainParent[n2];
}
// 같은 체인이 되었으면 마지막으로 같은 체인 내에서 연산
if(treeIdx[n1] > treeIdx[n2])
{
int tmp = n1;
n1 = n2;
n2 = tmp;
}
update(1, 1, N, treeIdx[n1] + 1, treeIdx[n2], value);
}
static int query(int treeNode, int s, int e, int left, int right) {
propagate(treeNode, s, e);
if(e < left || right < s)
return 0;
if(left<=s && e<= right)
return tree[treeNode];
int mid = (s + e) >> 1;
return Math.max(query(treeNode << 1, s, mid, left, right),
query(treeNode << 1 | 1, mid + 1, e, left, right));
}
static void update(int treeNode, int s, int e, int left, int right, int value) {
propagate(treeNode, s, e);
if(e < left || right < s)
return;
if(left<=s && e<= right)
{
lazy[treeNode] = value;
propagate(treeNode, s, e);
return;
}
int mid = (s + e) >> 1;
update(treeNode << 1, s, mid, left, right, value);
update(treeNode << 1 | 1, mid + 1, e, left, right, value);
tree[treeNode] = Math.max(tree[treeNode << 1] , tree[treeNode << 1 | 1]);
}
static void propagate(int treeNode, int s, int e) {
if(lazy[treeNode] != 0)
{
tree[treeNode] += lazy[treeNode];
if(s != e)
{
lazy[treeNode << 1] += lazy[treeNode];
lazy[treeNode << 1 | 1] += lazy[treeNode];
}
lazy[treeNode] = 0;
}
}
static void setHLD(int nowNode, int parentNode, int level) {
treeIdx[nowNode] = ++idx;
chainLevel[nowNode] = level;
for(int i=0; i<adNode[nowNode].size(); i++)
{
int next = adNode[nowNode].get(i);
if(next == parentNode)
continue;
if(i == 0) // heavy 노드는 체인 유지
{
chainHead[next] = chainHead[nowNode];
chainParent[next] = chainParent[nowNode];
setHLD(next, nowNode, level);
}
else // light는 새로운 채인 생성
{
chainHead[next] = next; // 새체인의 헤드는 자기자신
chainParent[next] = nowNode; // 이전 체인으로 점프를 위한 노드 세팅
setHLD(next, nowNode, level + 1);// 새 체인은 level이 1증가한다.
}
}
}
static void setSize(int nowNode, int parentNode, int[] size) {
int heavySize = 0;
int heavyIdx = 0;
size[nowNode] = 1;
for(int i=0; i<adNode[nowNode].size(); i++)
{
int next = adNode[nowNode].get(i);
if(next == parentNode)
continue;
setSize(next, nowNode, size);
size[nowNode] += size[next];
if(heavySize < size[next])
{
heavySize = size[next];
heavyIdx = i;
}
}
if(adNode[nowNode].size() > 0)
Collections.swap(adNode[nowNode], 0, heavyIdx);
}
}