티스토리 뷰
BOJ 7644. Highway Construction
트리 DP를 돌려 주며, 각 점이 경로의 LCA일 때의 답을 구해 주면 됩니다. 먼저 각 점에서 아래로 가장 먼 점까지의 거리를 전처리해 주고, 이것을 이용해 트리 DP를 할 수 있습니다. 어떤 점 $v$의 자식 $c$를 봅시다. $v$를 LCA라고 하면, 자식 최대 두 개를 골라 해당 자식을 경로에 포함시킬 수 있습니다. 경로에 포함되지 않는 자식의 경우 해당 서브트리에서 가장 먼 정점까지의 거리만이 중요하고, 경로에 포함되는 자식의 경우 트리 DP를 통해 올려준 값이 중요합니다. 해당 값들로 LCA가 $v$일 때의 답을 구한 후, 이번에는 자식을 하나만 골랐을 때의 답을 부모로 전달해 줍니다.
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int n;
vector<pair<int, ll> > link[100002];
ll maxDepth[100002];
vector<ll> vec[100002];
ll ans = LLONG_MAX;
void dfs1(int x, int par=-1){
for(auto y: link[x]){
if(y.first==par) continue;
dfs1(y.first, x);
maxDepth[x] = max(maxDepth[x], maxDepth[y.first] + y.second);
vec[x].push_back(maxDepth[y.first]+y.second);
}
sort(vec[x].rbegin(), vec[x].rend());
vec[x].push_back(0);
}
ll dfs2(int x, int par=-1, ll fromUp = 0){
vector<pair<ll, ll> > tVec;
for(auto y: link[x]){
if(y.first==par) continue;
ll tUp = max(fromUp, ((int)vec[x].size() > 1 && vec[x][0] == maxDepth[y.first]+y.second) ? vec[x][1] : vec[x][0])
+ y.second;
tVec.push_back(make_pair(maxDepth[y.first] + y.second, dfs2(y.first, x, tUp)));
assert(tVec.back().first >= tVec.back().second);
}
sort(tVec.rbegin(), tVec.rend());
for(int i=0; i<4; i++) tVec.push_back(make_pair(0, 0));
ans = min(ans, max({fromUp, tVec[0].second, tVec[1].second, tVec[2].first}));
return max(tVec[0].second, tVec[1].first);
}
int main(){
scanf("%d", &n);
if(!n) return 0;
ans = LLONG_MAX;
for(int i=1; i<=n; i++){
link[i].clear();
maxDepth[i] = 0;
vec[i].clear();
}
for(int i=1; i<n; i++){
int x, y; ll w;
scanf("%d %d %lld", &x, &y, &w);
link[x].push_back(make_pair(y, w));
link[y].push_back(make_pair(x, w));
}
dfs1(1);
dfs2(1);
printf("%lld\n", ans);
main();
}
BOJ 3878. 점 분리
빨간색 점들의 볼록 껍질과 파란색 점들의 볼록 껍질을 구한 후, 이들 사이에 교점이 없으면 됩니다. 선분 교차를 통해 변이 겹치는 지 확인한 후, 한 볼록 껍질이 다른 쪽 안에 완전히 포함되는 경우도 처리해 줄 수 있습니다. 다만 모든 기하 문제가 그렇듯이 예외가 정말 많이 나올 수 있는데, 그런 예외에 걸리지 않도록 꼼꼼히 코딩해야 합니다.
BOJ 11987. Train Fare
몇 가지 간단한 관찰을 통해 풀이를 이끌어 내야 하는 류의 문제입니다. 아래 사실을 관찰합시다.
- 초기 상태보다 최단거리가 늘어난 정점을 outdated vertex라고 하자. 간선 가중치를 늘리기만 해서 최단경로를 줄일 수는 없으므로, 한 번 outdated vertex가 된 정점은 영원히 outdated vertex가 된다.
- 초기에 정점 1에서 시작한 bfs를 돌려서, 각 간선 $e$에 대해 $e$의 양끝점의 거리가 같은 경우 간선은 의미 없으므로 제거해 줘도 된다. 간선의 양끝점의 거리가 다른 경우(이 경우 정확히 1 차이나게 된다), 정점 1에서 더 가까운 쪽을 시작점, 더 먼 쪽을 끝점으로 하는 방향 간선으로 바꿀 수 있다.
- 이렇게 그래프를 방향 그래프로 바꾼 뒤, 간선 $e$에 대해 해당 간선을 이용하는 모든 (최단)경로의 길이가 처음보다 커져 있다면 해당 간선을 outdated edge라고 부르자.
이제 outdated vertex와 outdated edge 간의 관계를 살펴 봅시다.
- 각 쿼리는 edge 하나를 잡아 (이미 outdated되지 않았다면) outdated edge로 만드는 것과 같다.
- 만약 어떤 점으로 들어오는 모든 간선이 outdated되어 있다면 해당 정점이 outdated vertex가 된다.
- outdated vertex에서 나가는 모든 간선은 자동으로 outdated edge가 된다.
이제 그래프 정점 indegree를 가지고 큐로 잘 다루는 흔한 유형이 되었습니다. 굳이 구현 방법을 설명하자면 위상 정렬 비슷한 느낌으로 하시면 됩니다.
BOJ 21341. Endgame
Alice가 이길 수 있는 지 판별하는 건 쉽습니다. 첫 움직임을 고정한 뒤, 두 번째 움직임으로 가능한 것이 존재하는지만 판별해 주면 됩니다. $O(N \log N)$ 정도에 쉽게 처리할 수 있습니다.
이제 Alice가 이길 수 없을 때를 계산합시다. Alice의 위치를 고정하고 나면, Bob이 승리할 수 있는지를 계산하는 것은 위와 똑같은 방법으로 가능합니다. 하지만 고정할 위치가 $O(N^2)$개인데, 한 번의 판별에 $O(N \log N)$ 시간이 들기 때문에 모든 위치를 시도해 볼 수는 없습니다.
여기서 아래 사실을 관찰해야 합니다.
- 만약 Alice가 무승부를 이끌어낼 수 있다면, Alice가 도달할 수 있는 칸의 비율이 \frac{1}{8}$ 이상이다.
증명은 간단한 카운팅입니다. $N$이 작을 때 저 비율이 굉장히 작아진다는 사실을 알 수 있고, $N=3$일 때의 최악의 케이스를 계산하면 확률이 저렇게 나옵니다. 그래서 랜덤하게 아무 칸이나 잡아서 판별하는 과정을 충분히 많이 하면 답을 찾을 수 있습니다. 저 같은 경우에는 60번 정도 아무 칸이나 잡은 뒤, Alice가 무승부를 이끌어낼 수 없다면 불가능으로 판정했습니다.
BOJ 10124. Couriers
구간에서 과반을 차지하는 원소가 있는지 판별하는 문제입니다. 과반이라고 하면 보통 랜덤을 이용한 접근을 먼저 생각하시는 분이 많은데, 과반을 쉽고 간단하게 판별할 수 있는 결정론적인 알고리즘인 보이어-무어 알고리즘(Boyer-Moore Majority Vote)이 존재합니다. 이 알고리즘은 이미 다른 분들의 블로그에서 잘 설명되어 있습니다.
보이어-무어 알고리즘을 알고 있다면 수열에서 세그먼트 트리를 구축하고, 각 노드가 담당하는 구간에 대한 보이어-무어 알고리즘의 결과값을 저장해 둡니다. 그리고 구간 쿼리를 날려서 최빈값이 될 수 있는 후보 값 하나를 구한 뒤, 그 값이 실제로 과반을 차지하는지 판별하면 됩니다. 이 판별은 단순한 전처리에 이분 탐색(lower_bound)를 통해 할 수 있습니다.
BOJ 1848. 동굴 탐험
정말 아무 생각을 거치지 않고 나오는 풀이는 다음과 같습니다.
- 시작점에서 다익스트라 알고리즘을 돌려, 다시 1번 정점으로 돌아가는 최단 거리를 구한다.
위 알고리즘이 동작하지 않는 이유는 같은 도로를 왕복한 거리가 답이 될 수 있기 때문입니다. 따라서 이런 경우가 없도록 처리를 해 주어야 합니다.
문제는 1번 정점을 지나는 사이클의 최단 길이를 구하는 것입니다. 사이클에서 1번 정점으로 돌아오기 직전에 방문하는 점의 번호를 $x$라고 고정합시다. 이때 현재까지의 가정과 조건을 만족하는 사이클의 최단 거리는 ($1$번 정점에서 $x$번 정점까지 거리 2 이상인 경로 중 최단 경로의 길이) + ($x \rightarrow 1$ 간선의 길이)가 됩니다. 왼쪽 항을 어떻게 구할까요?
다익스트라 알고리즘을 돌리는데, (현재 정점, 거리) pair에 하나의 원소를 더 저장합시다. 바로 처음 1번 정점에서 출발한 다음 막 들른 정점의 번호 $p$입니다. 또한 통상적인 다익스트라 알고리즘은 각 정점을 최대 한 번만 방문하는데, 여기서는 두 번까지 방문할 수 있도록 허용합시다. 이때 두 번 방문했을 때의 $p$값이 서로 달라야 합니다. (즉, 이미 이 정점을 방문한 상태에서 이전과 같은 $p$값을 가진 데이터가 들어온다면 그냥 무시해 주면 됩니다.)
다익스트라 알고리즘 과정에서 1번 정점과 인접한 정점 $x$에 도달했다면, 먼저 $p \neq x$인지 확인합시다. $p = x$라면 이미 사용한 간선을 왕복하는 것이기 때문에 안 됩니다. $p \neq x$라면 이 사이클은 답의 후보가 될 수 있습니다. 이렇게 1번 정점과 인접한 모든 정점에 대해서 답 후보를 찾아준 뒤 그 중 가장 거리가 짧은 것을 구하면 됩니다.
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
struct dat{
int x; ll y; int p;
dat(){}
dat(int x, ll y, int p): x(x), y(y), p(p){}
bool operator<(const dat &r)const{
return y>r.y;
}
};
int n, m;
vector<pair<int, ll> > link[10002];
priority_queue<dat> pq;
vector<dat> vec[10002];
ll ans = LLONG_MAX;
int main(){
scanf("%d %d", &n, &m);
for(int i=1; i<=m; i++){
int s, e, x, y;
scanf("%d %d %d %d", &s, &e, &x, &y);
link[s].push_back(make_pair(e, x));
link[e].push_back(make_pair(s, y));
}
for(auto p: link[1]){
pq.push(dat(p.first, p.second, p.first));
}
while(!pq.empty()){
dat tmp = pq.top(); pq.pop();
int x = tmp.x;
if((int)vec[x].size() >= 2 || ((int)vec[x].size() == 1 && vec[x][0].p == tmp.p)) continue;
vec[x].push_back(tmp);
for(auto y: link[x]){
if(y.first == 1){
if(x != tmp.p) ans = min(ans, tmp.y + y.second);
continue;
}
pq.push(dat(y.first, tmp.y+y.second, tmp.p));
}
}
printf("%lld", ans);
}
'코딩 > 문제풀이' 카테고리의 다른 글
Problem Solving Diary #7 (0) | 2022.01.13 |
---|---|
Problem Solving Diary #6 (0) | 2022.01.11 |
Problem Solving Diary #4 (0) | 2022.01.06 |
Problem Solving Diary #3 (0) | 2022.01.01 |
BOJ 7469 K번째 수 (in Q log N!) (4) | 2021.08.21 |