https://www.acmicpc.net/problem/10999

 

10999번: 구간 합 구하기 2

첫째 줄에 수의 개수 N(1 ≤ N ≤ 1,000,000)과 M(1 ≤ M ≤ 10,000), K(1 ≤ K ≤ 10,000) 가 주어진다. M은 수의 변경이 일어나는 회수이고, K는 구간의 합을 구하는 회수이다. 그리고 둘째 줄부터 N+1번째 줄까지 N개의 수가 주어진다. 그리고 N+2번째 줄부터 N+M+K+1번째 줄까지 세 개의 정수 a, b, c 또는 a, b, c, d가 주어지는데, a가 1인 경우 b번째 수부터 c번째 수에 d를 더하고, a가 2

www.acmicpc.net


문제 풀이 및 설명

문제의 분류는 세그먼트 트리 with Lazy Propagation 이다.

이문제를 처음 접했을때, lazy propagation에 대한 사전지식이 없다면 매우 어렵게 느낄 것이다. 

또한 구글링이나, 검색을통해 lazy propagation을 배우려고 할때에 세그먼트 트리의 update와 query의 작동방식을 알고 있다면, 금방  lazy propagation 도 이해할 수 있겠지만, 세그먼트 트리의 작동원리를 알지못하고 그저 코드를 외워서 문제를 풀어왔다면 lazy propagation풀이를 봐도 이해하지 못할 것이다. 이문제의 유형을 처음 접했다면, lazy propagation을 먼저 검색해보고, lazy propagation이 이해가 가지 않는다면, 세그먼트 트리를 다시 공부 할 필요가 있다. (다른 수많은 블로그에 엄청 자세하게 나와있다)

 


전체 코드

#include <iostream>
using namespace std;

#define MAX_N 1000001
#define endl '\n'
typedef long long ll;
ll seg[MAX_N * 4];
ll lazy[MAX_N * 4];

void update_tree(int now, int target, int start, int end, ll value) {
     if (target > end || target < start) return;
     if (start == end) {
          seg[now] = value;
          return;
     }
     int mid = (start + end) / 2;
     update_tree(now * 2, target, start, mid, value);
     update_tree(now * 2 + 1, target, mid + 1, end, value);
     seg[now] = seg[now * 2] + seg[now * 2 + 1];
}

void update_lazy(int now,int start, int end){
     if (lazy[now] != 0) {
          seg[now] += (end - start + 1) * lazy[now];
          // 자신의 자식 리프노드의 갯수만큼 segment tree에 더해줌
          if (start != end) { // 만약 리프노드가 아니라면
               lazy[now * 2] += lazy[now];
               lazy[now * 2 + 1] += lazy[now];
               // 자신의 자식들의 노드에게 lazy 전달
          }
          lazy[now] = 0; // 해당 세그먼트 트리 노드에 대해서 업데이트 완료 했음으로 lazy 초기화
     }
}

void update_range(int now, int left, int right, int start, int end, ll value) {
     update_lazy(now, start, end);
     if (left > end || right < start) return;
     if (left <= start && end <= right) {
          seg[now] += (end - start + 1) * value;
          if (start != end) {
               lazy[now * 2] += value;
               lazy[now * 2 + 1] += value;
          }
          return;
     }
     int mid = (start + end) / 2;
     update_range(now * 2, left, right, start, mid, value);
     update_range(now * 2 + 1, left, right, mid + 1, end, value);
     seg[now] = seg[now * 2] + seg[now * 2 + 1];
}

ll query(int now, int left, int right, int start, int end) {
     update_lazy(now, start, end);
     if (left >end || right < start) return 0;
     if (left <= start && end <= right) {
          return seg[now];
     }
     int mid = (start + end) / 2;
     return (
          query(now * 2, left, right, start, mid) +
          query(now * 2 + 1, left, right, mid + 1, end)
          );
}

int main() {
     ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
     int N, M, K;
     cin >> N >> M >> K;
     for (int i = 0; i < N; i++) {
          ll data; cin >> data;
          update_tree(1, i, 0, N - 1, data);
     }
     for (int i = 0; i < M + K; i++) {
          int tag; cin >> tag;
          if (tag == 1) { // b ~ c , d update
               int b, c; ll value;
               cin >> b >> c >> value;
               update_range(1, b - 1, c - 1, 0, N - 1, value);
          }
          else { // b ~ c 구간합
               int b, c; cin >> b >> c;
               cout << query(1, b - 1, c - 1, 0, N - 1) << endl;
          }
     }
}

'알고리즘 > [C++]BOJ' 카테고리의 다른 글

[백준BOJ] 1395 스위치  (0) 2019.10.04
[백준BOJ] 1626 두 번째로 작은 스패닝 트리  (0) 2019.10.03
[백준BOJ] 15481 그래프와 MST  (0) 2019.10.03

https://www.acmicpc.net/problem/1626

 

1626번: 두 번째로 작은 스패닝 트리

첫째 줄에 그래프의 정점 수 V(1 ≤ V ≤ 50,000)와 에지 수 E(1 ≤ E ≤ 200,000)가 들어온다. 둘째 줄부터 E+1번째 줄까지 한 에지로 연결된 두 정점과 그 에지의 가중치가 주어진다. 음수 가중치는 없으며, 답은 int 범위를 넘지 않는다. 정점 번호는 1보다 크거나 같고, V보다 작거나 같은 자연수이다.

www.acmicpc.net


이 문제를 해결 하기 위해서는 우선 밑의 문제를 풀 수 있어야 한다.

https://www.acmicpc.net/problem/15481

 

15481번: 그래프와 MST

첫째 줄에 정점의 개수 N과 간선의 개수 M (2 ≤ N ≤ 200,000, N-1 ≤ M ≤ 200,000) 둘째 줄부터 M개의 줄에 간선의 정보 u, v, w가 주어진다. u와 v를 연결하는 간선의 가중치가 w라는 뜻이다. (1 ≤ u, v ≤ n, u ≠ v, 1 ≤ w ≤ 109) 

www.acmicpc.net

위의 문제와 비슷한 아이디어로 풀긴 하지만 좀 더 까다롭다.

(초기 MST를 구성하면서, 모든 정점을 방문하지 못한다면, -1를 출력하면 된다.)

{u , v , w} 의 정보를 갖는 새로운 간선을 삽입 할 경우

(기존 mst의 간선의 총합)-(u와v의 간선중 가장 큰 경로)+(w)

가 기존 (기존 mst의 간선의 총합) 보다 큰 것중에 가장 작은 값이 곧 답이 된다.

그러나, (u와v의 간선중 가장 큰 경로) == (w)

인경우를 생각 해보면 결국,

(기존 mst의 간선의 총합)-(u와v의 간선중 가장 큰 경로)+(w) == (기존 mst의 간선의 총합)

이 되어버리기 때문에, 실제로는 다른 mst가 존재함에도 불구하고 conitnue 하게 된다.

그렇기 때문에, 첫번째로 큰 간선이 w와 같은 경우에

두번째로 큰 간선을 찾아서 똑같이 적용시키면 된다.


#include <iostream>
#include <vector>
#include <algorithm>
#include <queue>
#include <limits.h>
#include <cstring>
using namespace std;

#define MAX_N 50001
#define MAX_PAR 20
#define SWAP(a,b) {int t= b; b = a; a = t;}
typedef long long ll;

int par[MAX_N][MAX_PAR]; // 부모 배열
int MAX[MAX_N][MAX_PAR]; // 최댓값 배열
int depth[MAX_N]; // 깊이 배열
bool visit[MAX_N]; // DFS 체크

// 입력 순서대로 우선 저장될 edge 정보
class edge {
public:
     int u, v, w;
     edge() {}
     edge(int u, int v, int w) {
          this->u = u;
          this->v = v;
          this->w = w;
     }
};
vector<edge> edges; // 입력 순서대로 저장

// MST를 위한 간선
vector<pair<int, int>> map[MAX_N];

// PQ에 들어갈 아이템 ( cost 순으로 정렬 됨 )
class PQItem {
public:
     int now, nowDepth, prev, prevDist;
     PQItem() {}
     PQItem(int now, int nowDepth, int prev, int prevDist) {
          this->now = now;
          this->nowDepth = nowDepth;
          this->prev = prev;
          this->prevDist = prevDist;
     }
     bool operator < (const PQItem & b) const {
          return this->prevDist < b.prevDist;
     }
     bool operator > (const PQItem & b) const {
          return this->prevDist > b.prevDist;
     }
};
priority_queue<PQItem, vector<PQItem>, greater<> > pq;

// 입력받을 V, E
int V, E;
ll MST() {
     int cnt = 0, ret = 0; 
     pq.push(PQItem(1, 0, 1, 0)); // 1번노드부터 pq push
     while (!pq.empty()) {
          PQItem top = pq.top(); pq.pop();
          int now = top.now;
          int prev = top.prev;
          int prevDist = top.prevDist;
          int nowDepth = top.nowDepth;
          if (visit[now]) continue;
          par[now][0] = prev; // 1번째 부모
          MAX[now][0] = prevDist; // 부모와의 dist
          depth[now] = nowDepth; // 현재 depth
          ret += prevDist; // mst 간선의 총합을 위한 리턴값
          visit[now] = true; // visit 체크
          cnt++; // MST의 존재여부 파악을 위함
          for (int i = 0; i < map[now].size(); ++i) {
               if (!visit[map[now][i].first]) {
                    pq.push(PQItem(map[now][i].first, nowDepth + 1, now, map[now][i].second));
               }
          }
     }
     if (cnt != V) ret = -1; // mst가 존재하지않는다 ( = 한 정점에서 모든 정점을 방문 할 수 없다 )
     return ret;
}

// lca 알고리즘
int LCA(int x, int y) {
     if (depth[x] > depth[y]) SWAP(x, y);

     for (int i = 19; i >= 0; i--) {
          int diff = depth[y] - depth[x];
          if (diff >= (1 << i)) {
               y = par[y][i];
          }
     }
     if (x == y) return x;
     for (int i = 19; i >= 0; i--) {
          if (par[x][i] != par[y][i]) {
               x = par[x][i];
               y = par[y][i];
          }
     }
     return par[x][0];
}

// x노드와 y노드 사이의 간선의 최댓값 리턴
int getMax(int x, int y) {
     int lca = LCA(x, y);
     int ret = -1;
     int x_to_lca = depth[x] - depth[lca];
     int y_to_lca = depth[y] - depth[lca];
     for (int i = 19; i >= 0; i--) {
          if (x_to_lca >= (1 << i)) {
               ret = max(ret, MAX[x][i]);
               x_to_lca -= (1 << i);
               x = par[x][i];
          }
     }

     for (int i = 19; i >= 0; i--) {
          if (y_to_lca >= (1 << i)) {
               ret = max(ret, MAX[y][i]);
               y_to_lca -= (1 << i);
               y = par[y][i];
          }
     }
     return ret;
}

// x노드와 y 노드 사이의 2번째로 큰 ( prevMax보다 첫번째로 작은 ) 값 리턴
// O(N)으로 탐색했음 ( 더좋은 방법이 있을 것 같음 )
int getSecondMax(int x, int y, int prevMax) {
     int lca = LCA(x, y);
     int x_to_lca = depth[x] - depth[lca];
     int y_to_lca = depth[y] - depth[lca];
     int ret = -1;
     for (int i = 0; i < x_to_lca; ++i) {
          if (MAX[x][0] < prevMax) {
               ret = max(ret, MAX[x][0]);
          }
          x = par[x][0];
     }

     for (int i = 0; i < y_to_lca; ++i) {
          if (MAX[y][0] < prevMax) {
               ret = max(ret, MAX[y][0]);
          }
          y = par[y][0];
     }
     return ret;
}

int main() {
     ios::sync_with_stdio(false);
     cin.tie(0); cout.tie(0);
     cin >> V >> E;
     for (int i = 0; i < E; ++i) {
          int a, b, w; cin >> a >> b >> w;
          edges.push_back(edge(a, b, w));
          map[a].push_back({ b,w });
          map[b].push_back({ a,w });
     }

     ll minimum = MST();
     if (minimum == -1) {
          cout << -1 << "\n";
          return 0;
     }

     for (int i = 1; i <= 19; ++i) {
          for (int j = 1; j <= V; j++) {
               par[j][i] = par[par[j][i - 1]][i - 1];
               MAX[j][i] = max(MAX[j][i - 1], MAX[par[j][i - 1]][i - 1]);
          }
     }

     int ans = INT_MAX;
     for (int i = 0; i < E; ++i) {
          int u, v, w;
          u = edges[i].u;
          v = edges[i].v;
          w = edges[i].w;
          int maxE = getMax(u, v);
          // 가장 큰 값과 비교
          if (minimum - maxE + w > minimum) {
               if (ans > minimum - maxE + w) ans = minimum - maxE + w;
          }
          // 가장 큰 값이 현재 삽입하려는 간선의 weight와 같다면
          // 두번 째로 큰 간선을 찾는다
          else if (minimum - maxE + w == minimum) {
               int secondMaxE = getSecondMax(u, v, maxE);
               // 찾았 다면
               if (secondMaxE != -1) {
                    if (ans > minimum - secondMaxE + w) ans = minimum - secondMaxE + w;
               }
          }
     }
     if (ans == INT_MAX) cout << -1 << "\n";
     else cout << ans << "\n";
}

'알고리즘 > [C++]BOJ' 카테고리의 다른 글

[백준BOJ] 10999 구간 합 구하기 2  (0) 2019.10.04
[백준BOJ] 15481 그래프와 MST  (0) 2019.10.03
[백준BOJ] 15480_LCA와 쿼리  (0) 2019.10.03

https://www.acmicpc.net/problem/15481

 

15481번: 그래프와 MST

첫째 줄에 정점의 개수 N과 간선의 개수 M (2 ≤ N ≤ 200,000, N-1 ≤ M ≤ 200,000) 둘째 줄부터 M개의 줄에 간선의 정보 u, v, w가 주어진다. u와 v를 연결하는 간선의 가중치가 w라는 뜻이다. (1 ≤ u, v ≤ n, u ≠ v, 1 ≤ w ≤ 109) 

www.acmicpc.net


MST 알고리즘 ( prim 알고리즘 사용 ) + LCA 알고리즘을 이용한 문제풀이가 가능하다.

우선 사용될 변수(배열)들은 다음과 같다.

bool visit[MAX_N]; // for mst
int par[MAX_N][21]; // 부모 배열
int dist[MAX_N][21]; // 두노드간의 거리의 최대 간선의 weight가 저장될 배열
int depth[MAX_N]; // for lca 

입력된 간선들로 MST를 만들면서 depth, dist, par 배열 완성하게 되면

실제로 MST를 이루는 간선의 갯수는 결국 N - 1 개가 된다.

다음의 예제를 통한 완성 된 결과를 보자

위의 결과로 가장 최선의 MST 는 10 이다

그럼 첫번째 간선에 대해서 확인해보자.

1번과 2번을 이어주게되면 

길이 4 또는 2를 제거해야 하는데 당연히 4를 제거 해야 5가 포함된 mst를 완성할 수 있다.

(즉, 1번과 2번노드사이의 최대 길이 경로 하나를 삭제하면 된다 )

다른 케이스를 확인해보자.

이경우에는 길이 4의 간선을 삭제( 4번노드과 1번노드 사이의 가장 긴 경로의 간선 )

을 하게되면 답이 10( 원래 mst ) - 4 ( 삭제된 간선 ) + 9 ( 삽입된 간선 ) = 15. 이된다.


< 전체 코드 >

#include <iostream>
#include <vector>
#include <algorithm>
#include <queue>
#include <algorithm>
using namespace std;

#define MAX_N 200001
#define swap(a,b) {int t= b ; b = a; a = t;}


// DFS( mst )를 위한 vector
vector<pair<int, int> > map[MAX_N];

// 간선을 순서대로 저장하기위한 클래스
class edge {
public:
     int u, v, w;
     edge() {}
     edge(int u, int v, int w) {
          this->u = u;
          this->v = v;
          this->w = w;
     }
};

// 순서대로 저장되어있는 vector
vector<edge> edges;

bool visit[MAX_N]; // for mst
int par[MAX_N][21]; // 부모 배열
int dist[MAX_N][21]; // 두노드간의 거리의 최대 간선의 weight가 저장될 배열
int depth[MAX_N]; // for lca 

// pq에 들어갈 item ( cost 순으로 정렬되어 pop 됨 )
class pqItem {
public:
     int n, cost, prev, depth;
     pqItem() {}
     pqItem(int n, int cost, int prev, int depth) {
          this->n = n;
          this->cost = cost;
          this->prev = prev;
          this->depth = depth;
     }
     bool operator > (const pqItem & b) const {
          return this->cost > b.cost;
     }
     bool operator < (const pqItem & b) const {
          return this->cost < b.cost;
     }
};
// cost 가 제일 작은 것이 pop 됨.
priority_queue< pqItem, vector<pqItem>, greater<> > pq;

// mst 를 돌면서, 부모의 정보와, dist 정보를 저장
long long mst() {
     long long ret = 0;
     pq.push(pqItem(1, 0,1,0));
     while (!pq.empty()) {
          pqItem front = pq.top(); pq.pop();
          
          int now = front.n;
          int cost = front.cost;
          int nowDepth = front.depth;
          int prev = front.prev;
          
          if (visit[now]) continue;
          visit[now] = true;
          
          ret += cost;
          par[now][0] = prev; // 2^0 부모 설정
          dist[now][0] = cost; // 2^0 부모까지의 최대 거리 설정
          depth[now] = nowDepth; // lca 알고리즘을 위한 depth 설정
          for (int i = 0; i < map[now].size(); i++) {
               if (visit[map[now][i].first] == false) {
                    pq.push(pqItem(map[now][i].first, map[now][i].second, now, nowDepth + 1));
               }
          }
     }
     return ret;
}


int lca(int x, int y) {
     if (depth[x] > depth[y]) swap(x, y);
     for (int i = 19; i >= 0; i--) {
          int diff = depth[y] - depth[x];
          if (diff >= (1 << i)) {
               y = par[y][i];
          }
     }
     if (x == y) return x;
     for (int i = 19; i >= 0; i--) {
          if (par[x][i] != par[y][i]) {
               x = par[x][i];
               y = par[y][i];
          }
     }
     return par[x][0];
}


int main() {
     ios::sync_with_stdio(false);
     cin.tie(0); cout.tie(0);
     int N, M;
     cin >> N >> M;
     for (int i = 0; i < M; i++) {
          int u, v, w; cin >> u >> v >> w;
          map[u].push_back({ v,w });
          map[v].push_back({ u,w });
          edges.push_back( edge(u, v, w) );
     }
     long long minimum = mst();
     for (int i = 1; i <= 19; i++) {
          for (int j = 1; j <= N; j++) {
               par[j][i] = par[par[j][i - 1]][i - 1];
               dist[j][i] = max(dist[j][i-1],dist[par[j][i - 1]][i - 1]);
          }
     }

     for (int i = 0; i < edges.size(); i++) {
          int u = edges[i].u;
          int v = edges[i].v;
          int w = edges[i].w;
          long long ret = minimum + w;
          int lca_node = lca(u, v);
          int u_to_lca = depth[u] - depth[lca_node];
          int v_to_lca = depth[v] - depth[lca_node];
          int maxAns = -1;
          for (int j = 19; j >= 0; j--) {
               if (u_to_lca >= (1 << j)) {
                    maxAns = max(maxAns, dist[u][j]);
                    u_to_lca -= (1 << j);
                    u = par[u][j];
               }
          }
          for (int j = 19; j >= 0; j--) {
               if (v_to_lca >= (1 << j)) {
                    maxAns = max(maxAns, dist[v][j]);
                    v_to_lca -= (1 << j);
                    v = par[v][j];
               }
          }
          cout << ret - maxAns << "\n";
     }
}

https://www.acmicpc.net/problem/15480

 

15480번: LCA와 쿼리

첫째 줄에 정점의 개수 N(1 ≤ N ≤ 100,000)이 주어진다. 둘째 줄부터 N-1개의 줄에는 트리 T의 간선 정보 u와 v가 주어지다. u와 v는 트리의 간선을 나타내는 두 정점이다. 다음 줄에는 쿼리의 개수 M(1 ≤ M ≤ 100,000)가 주어진다. 다음 M개의 줄에는 쿼리를 나타내는 r, u, v가 주어진다.

www.acmicpc.net


 

{root, a} = v1

{root, b} = v2

{a,b} = v3

라고 하였을때

depth[v1], depth[v2], depth[v3] 중 가장 큰 노드가 답이 된다.

 

case 1 ) a 와 b 둘다 root의 자손일 경우

case 2) a 또는 b 만 root의 자손일 경우

case 3 ) a와 b 둘다 root의 자손이 아닐 경우 

 

의 case를 생각해보며 식을 적용해보면 왜 그러한지 알 수 있다.

 


#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

#define MAX_N 100001
#define swap(a,b) {int t = b; b = a; a = t;}
vector<int> vec[MAX_N];
bool visit[MAX_N];
int depth[MAX_N];
int par[MAX_N][20];

vector<pair<int, int> > tmp; // first : depth, second : node
bool cmp(pair<int, int> a, pair<int, int> b) {
     return a.first > b.first;
}

void dfs(int now, int prev, int prevDepth) {
     visit[now] = true;
     par[now][0] = prev;
     depth[now] = prevDepth;
     
     for (int i = 0; i < vec[now].size(); i++) {
          if (visit[vec[now][i]] == false) {
               dfs(vec[now][i], now, prevDepth + 1);
          }
     }
}

int lca(int x, int y) {
     if (depth[x] > depth[y]) swap(x, y);
     for (int i = 19; i >= 0; i--) {
          int diff = depth[y] - depth[x];
          if ((diff >= (1 << i))) y = par[y][i];
     }
     if (x == y) return x;
     for (int i = 19; i >= 0; i--) {
          if (par[x][i] != par[y][i]) {
               x = par[x][i];
               y = par[y][i];
          }
     }
     return par[x][0];
}

int main() {
     ios::sync_with_stdio(false);
     cin.tie(0); cout.tie(0);
     int N; cin >> N;
     for (int i = 0; i < N - 1; i++) {
          int a, b; cin >> a >> b;
          vec[a].push_back(b);
          vec[b].push_back(a);
     }
     dfs(1, 1, 0);
     for (int i = 1; i <= 19; i++) {
          for (int j = 1; j <= N; j++) {
               par[j][i] = par[par[j][i - 1]][i - 1];
          }
     }
     int M, ret; cin >> M;
     for (int i = 0; i < M; i++) {
          int root, a, b; cin >> root >> a >> b;
          int root_a, root_b, a_b;
          root_a = lca(root, a);
          root_b = lca(root, b);
          a_b = lca(a, b);
          tmp.push_back({ depth[root_a], root_a });
          tmp.push_back({ depth[root_b], root_b });
          tmp.push_back({ depth[a_b], a_b });
          sort(tmp.begin(), tmp.end(), cmp);
          cout << tmp[0].second << "\n";
          tmp.clear();
     }
}

 

논리적으로 뭔가 해보려고 한참을 고민하다 결국 구글링으로 해답을 찾아본 문제이다.

여러 솔루션들을 보았는데, 논리적 설명보단 테스트케이스를 통해 식을 유추한뒤에

답을 도출해낸 방식이 대다수 이다.

방법이 쉽게 생각나지 않는다면, 테스트 케이스 그대로 따라해보면서 식을 유도하는 것도

실력의 일부가 되는 것 같다.

 

https://www.acmicpc.net/problem/3176

 

3176번: 도로 네트워크

문제 N개의 도시와 그 도시를 연결하는 N-1개의 도로로 이루어진 도로 네트워크가 있다.  모든 도시의 쌍에는 그 도시를 연결하는 유일한 경로가 있고, 각 도로의 길이는 입력으로 주어진다. 총 K개의 도시 쌍이 주어진다. 이때, 두 도시를 연결하는 경로 상에서 가장 짧은 도로의 길이와 가장 긴 도로의 길이를 구하는 프로그램을 작성하시오. 입력 첫째 줄에 N이 주어진다. (2 ≤ N ≤ 100,000) 다음 N-1개 줄에는 도로를 나타내는 세 정수 A, B

www.acmicpc.net


부모 테이블을 생성할 때에

같은 논리로

MAX 와 MIN 테이블을 만들어 주면 된다.

par[ i ][ k ] : i의 2^k번째 부모의 노드

MAX[ i ][ k ] : i의 2^k번쨰 부모까지의 최대값

MIN[ i ][ k ] : i의 2^k번쨰 부모까지의 최솟값

    for (int i = 1; i <= 19; i++) {
          for (int j = 1; j <= N; j++) {
               par[j][i] = par[par[j][i - 1]][i - 1];
               MAX[j][i] = max(MAX[j][i - 1], MAX[par[j][i - 1]][i - 1]);
               MIN[j][i] = min(MIN[j][i - 1], MIN[par[j][i - 1]][i - 1]);
          }
     }

전체코드

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
typedef long long ll;
#define SWAP(a,b) {int t = b; b = a; a = t;}
#define MAX_N 100002

int depth[MAX_N];
int MAX[MAX_N][20];
int MIN[MAX_N][20];
int par[MAX_N][20];
bool visit[MAX_N];
vector<pair<int,int>> vec[MAX_N];

void dfs(int now,int prev, int prevDis, int prevDepth) {
     visit[now] = true;
     depth[now] = prevDepth;
     MAX[now][0] = prevDis;
     MIN[now][0] = prevDis;
     par[now][0] = prev;
     for (int i = 0; i < vec[now].size(); i++) {
          if (visit[vec[now][i].first] == false) {
               dfs(vec[now][i].first, now, vec[now][i].second, prevDepth + 1);
          }
     }
}

int lca(int x, int y) {
     if (depth[x] > depth[y]) SWAP(x, y);

     for (int i = 19; i >= 0; i--) {
          int diff = depth[y] - depth[x];
          if (diff >= (1 << i)) {
               y = par[y][i];
          }
     }
     if (x == y) return x;
     for (int i = 19; i >= 0; i--) {
          if (par[x][i] != par[y][i]) {
               x = par[x][i];
               y = par[y][i];
          }
     }
     return par[x][0];
}



int main() {
     ios::sync_with_stdio(false);
     cin.tie(0);
     cout.tie(0);
     int N; cin >> N;
     for (int i = 0; i < N-1; i++) {
          int a, b, w; cin >> a >> b >> w;
          vec[a].push_back({ b,w });
          vec[b].push_back({ a,w });
     }
     dfs(1, 1, 0, 0);
     for (int i = 1; i <= 19; i++) {
          for (int j = 1; j <= N; j++) {
               par[j][i] = par[par[j][i - 1]][i - 1];
               MAX[j][i] = max(MAX[j][i - 1], MAX[par[j][i - 1]][i - 1]);
               MIN[j][i] = min(MIN[j][i - 1], MIN[par[j][i - 1]][i - 1]);
          }
     }
     int M; cin >> M;
     for (int i = 0; i < M; i++) {
          int x, y; cin >> x >> y;
          int lca_node = lca(x, y);
          int x_to_lca = depth[x] - depth[lca_node];
          int y_to_lca = depth[y] - depth[lca_node];
          int maxAns = -1;
          int minAns = 1e9;
          for (int j = 19; j >= 0; j--) {
               if (x_to_lca >= (1 << j)) {
                    maxAns = max(maxAns, MAX[x][j]);
                    minAns = min(minAns, MIN[x][j]);
                    x_to_lca -= (1 << j);
                    x = par[x][j];
               }
          }
          for (int j = 19; j >= 0; j--) {
               if (y_to_lca >= (1 << j)) {
                    maxAns = max(maxAns, MAX[y][j]);
                    minAns = min(minAns, MIN[y][j]);
                    y_to_lca -= (1 << j);
                    y = par[y][j];
               }
          }
          cout << minAns << " " << maxAns << "\n";
     }
}

https://www.acmicpc.net/problem/13511

 

13511번: 트리와 쿼리 2

N개의 정점으로 이루어진 트리(무방향 사이클이 없는 연결 그래프)가 있다. 정점은 1번부터 N번까지 번호가 매겨져 있고, 간선은 1번부터 N-1번까지 번호가 매겨져 있다. 아래의 두 쿼리를 수행하는 프로그램을 작성하시오. 1 u v: u에서 v로 가는 경로의 비용을 출력한다. 2 u v k: u에서 v로 가는 경로에 존재하는 정점 중에서 k번째 정점을 출력한다. k는 u에서 v로 가는 경로에 포함된 정점의 수보다 작거나 같다.

www.acmicpc.net


DFS와 점화식을 이용하여 parser_table( dp table )를 할 수 없다면 다른 쉬운 LCA 문제를 먼저 푸는게 좋을 것 같다.

 

u에서 v로 가는데 k번째 노드번호를 출력하는 문제이다.

u에서 v로 가는 경로를 생각해보면

u -> (u와 v의 lca) -> v

의 경로가 될 것이다.

u가 1번째 노드라고 한다면

k번째 노드가 lca 보다 왼쪽에 있는지 오른쪽에 있는지 판단한 뒤에

u 또는 v 부터 diff 만큼 올라간 노드가 답이 된다 !


#include <iostream>
#include <vector>
using namespace std;

#define MAX_N 100001
#define SWAP(a,b) {int t = b;  b = a; a = t;}
typedef long long ll;

vector<pair<int,int>> vec[MAX_N];
ll dis[MAX_N];
int depth[MAX_N];
int par[MAX_N][20];
bool visit[MAX_N];

void dfs(int now, int prev, ll nowDis, int nowDepth) {
     visit[now] = true;
     par[now][0] = prev;
     dis[now] = nowDis;
     depth[now] = nowDepth;
     for (int i = 0; i < vec[now].size(); i++) {
          if (visit[ vec[now][i].first] == false) {
               dfs(vec[now][i].first, now, nowDis + vec[now][i].second, nowDepth + 1);
          }
     }

}

int lca(int x, int y) {
     if (depth[x] > depth[y]) SWAP(x, y);

     for (int i = 19; i >= 0; i--) {
          int diff = depth[y] - depth[x];
          if (diff >= (1 << i)) {
               y = par[y][i];
          }
     }
     if (x == y) return x;
     for (int i = 19; i >= 0; i--) {
          if (par[x][i] != par[y][i]) {
               x = par[x][i];
               y = par[y][i];
          }
     }
     return par[x][0];
}

int getK(int x, int y, int k) {
     int lcaNode = lca(x, y);
     int x_to_lca = depth[x] - depth[lcaNode];
     int y_to_lca = depth[y] - depth[lcaNode];
     if (k <= x_to_lca + 1) {
          int diff = k - 1;
          for (int i = 19; i >= 0; i--) {
               if (diff >= (1 << i)) {
                    diff -= (1 << i);
                    x = par[x][i];
               }
          }
          return x;
     }
     else {
          int diff = (y_to_lca)-(k - (x_to_lca + 1));
          for (int i = 19; i >= 0; i--) {
               if (diff >= (1 << i)) {
                    diff -= (1 << i);
                    y = par[y][i];
               }
          }
          return y;
     }

}

int main() {
     ios::sync_with_stdio(false);
     cin.tie(0); cout.tie(0);
     int N; cin >> N;
     for (int i = 0; i < N-1; i++) {
          int a, b, w;   cin >> a >> b >> w;
          vec[a].push_back({ b,w });
          vec[b].push_back({ a,w });
     }
     
     dfs(1, 1, 0, 0);
     for (int i = 1; i <= 19; i++) {
          for (int j = 1; j <= N; j++) {
               par[j][i] = par[par[j][i - 1]][i - 1];
          }
     }

     int M; cin >> M;
     for (int i = 0; i < M; i++) {
          int tag; cin >> tag;
          if (tag == 1) {
               int x, y; cin >> x >> y;
               cout << dis[x] + dis[y] - 2 * dis[lca(x, y)] << "\n";
          }
          else {
               int x, y, k; cin >> x >> y >> k;
               cout << getK(x, y, k) << "\n";
          }
     }
}

https://www.acmicpc.net/problem/8012

 

8012번: 한동이는 영업사원!

문제 한동이는 경상도내를 돌아다니면서 열심히 일하는 영업사원이다. 예전에는 자기가 원하는 도시만 골라서 다닐 수 있었지만 시대가 바뀌어서 이제 그렇게는 하지 못하게 되었다. 시대가 바뀜에 따라, 한동이는 회사에서 돌아야 할 도시의 목록을 받고, 그 목록대로 도시를 여행한다. 회사에서 주는 여행지 목록은 정말 안타깝게도 최적화되어 있지가 않다. 여행을 떠나기 전에 한동이는 모든 도시를 방문하는데 걸리는 최소의 시간을 알고싶어하는데, 한동이는 경영학과라 컴퓨

www.acmicpc.net


두 정점간의 거리는 ( 각 노드에서 노드가 길이가 1일경우 )

int distance = depth[node1] + depth[node2] - 2 * depth[lca(node1, node2)];

가 되는데 코드 그대로 해석하자면 

root ~ node1 까지의 거리

+

root ~ node2 까지의 거리

-

2 * (root ~ (node1과 node2의 LCA) 까지의 거리 )

가 된다.

 

이문제에서 헷갈렸던 점은 한동이는 root에서 첫번째 방문도시로 순간이동 한다고 가정하고 풀어야 한다..

( 문제에 설명이 부족하다고 생각한다 )


#include <iostream>
#include <vector>
#include <cstring>
using namespace std;

#define MAX_N 30001
#define SWAP(a,b) {int t = b; b = a; a = t;}
vector<int> vec[MAX_N];
bool visit[MAX_N];
int depth[MAX_N];
int par[MAX_N][20];
int dis[MAX_N];
vector<int> m_vec;
void dfs(int now, int nowDepth, int prev) {
     visit[now] = true;
     depth[now] = nowDepth;
     par[now][0] = prev;
     for (int i = 0; i < vec[now].size(); i++) {
          if (visit[vec[now][i]] == false) {
               dfs(vec[now][i], nowDepth + 1, now);
          }
     }
}


int lca(int x, int y) {
     int ret = 0;
     if (depth[x] > depth[y]) SWAP(x, y);

     for (int i = 19; i >= 0; i--) {
          int diff = depth[y] - depth[x];
          if (diff >= (1 << i)) {
               y = par[y][i];
          }
     }
     if (x == y) return x;
     for (int i = 19; i >= 0; i--) {
          if (par[x][i] != par[y][i]) {
               x = par[x][i];
               y = par[y][i];
          }
     }
     return par[x][0];
}

int main() {
     ios::sync_with_stdio(false);
     cin.tie(0);
     cout.tie(0);
     int N, M;
     cin >> N;
     for (int i = 0; i < N - 1; i++) {
          int a, b;
          cin >> a >> b;
          vec[a].push_back(b);
          vec[b].push_back(a);
     }
     dfs(1, 0, 1);
     for (int j = 1; j <= 19; j++) {
          for (int i = 1; i <= N; i++) {
               par[i][j] = par[par[i][j - 1]][j - 1];
          }
     }
     cin >> M;
     int prev, now;
     long long ret = 0;
     for (int i = 0; i < M; i++) {
          cin >> now;
          if (i)  ret += depth[prev] + depth[now] - 2 * depth[lca(now, prev)];
          prev = now;
     }
     cout << ret << "\n";
     
}

'알고리즘 > [C++]BOJ' 카테고리의 다른 글

[백준BOJ] 13511_트리와 쿼리2 (C++)  (0) 2019.10.02
[백준BOJ] 11437_LCA (C++)  (0) 2019.10.02
[백준] 2269_수들의합 (C++)  (0) 2019.10.01

https://polohee81.tistory.com/17

 

[백준BOJ] 11437_LCA (C++)

https://www.acmicpc.net/problem/11437 11437번: LCA 첫째 줄에 노드의 개수 N이 주어지고, 다음 N-1개 줄에는 트리 상에서 연결된 두 정점이 주어진다. 그 다음 줄에는 가장 가까운 공통 조상을 알고싶은 쌍의..

polohee81.tistory.com

위의 링크의 풀이와, 코드가 똑~~~~~~~~~~~~~~~~~~같다.

+ Recent posts