https://oj.uz/problem/view/IOI18_highway
문제 보기 - 통행료 (IOI18_highway) :: oj.uz
문제 보기 - 통행료 (IOI18_highway)
oj.uz
모든 서브태스크에서, 도시가 모두 한산할 때의 통행료를 $w_0$이라고 한다. 이것은 1개의 쿼리를 소모해 미리 구해놓는다.
1. 5점 풀이(Subtask 1)
쿼리가 100개, 도로가 99개이므로 모든 도로에 대해 그 도로만 혼잡하게 한 다음 통행료 $w$를 계산하면 $w > w_0$인 도로들이 경로를 만들게 된다. 이 경로의 양 끝 점에 있는 두 도시가 $S$와 $T$가 된다.
2. 12점 풀이(Subtask 1, 2)
트리상에서 두 도시 $S$와 $T$의 거리는 $D = \frac{w_0}{A}$이다. 도시 0과의 거리가 $D$인 도시들에 대해서 이분탐색을 한다. 각 도시에 대해 그 도시와 연결된 모든 도로를 혼잡하게 만들어도 다른 도시들에게는 영향을 주지 않으므로 이분탐색이 가능하다.
3. 6점 풀이(Subtask 3)
주어지는 그래프는 직선이다. 0에 가까운 도시와 $N$에 가까운 도시를 각각 이분탐색 한 번으로 구할 수 있다. 한쪽 끝부터 도로들을 혼잡하게 만들었을 때, 통행료가 $w_0$과 같았다가 커지는 도로를 찾으면 된다.
4. 51점 풀이(Subtask 1, 2, 3, 4)
도시에 대해 이분탐색을 진행한다. 몇 개의 도시를 고르고 그 도시와 연결된 도로를 모두 혼잡하게 만든 뒤 통행료가 커졌다면, 고른 몇 개의 도시 중 하나는 $S$ 또는 $T$이다. 이렇게 하면 수많은 반례가 존재한다. 이분탐색으로 찾는 도시는 $S$나 $T$가 아닌 $S$와 $T$ 사이의 도시가 될 수도 있기 때문이다. Subtask 3에서 양 끝점을 구할 수 있던 이유를 생각해보자. $S$와 $T$가 경로상에서 0과 가장 가까운 점이거나 $N$과 가장 가까운 점이기 때문에 이분탐색이 성립했다.
따라서 트리에서도 이분탐색을 가능하게 하려면, 도시들의 순서를 다음 조건을 만족할 수 있도록 잘 골라줘야 한다.
$S$와 $T$를 잇는 경로상의 도시 중 가장 먼저 등장하는 도시는 $S$ 또는 $T$이다.
bfs를 돌면서 방문하는 순서대로 번호를 매겨주고, 이 번호를 뒤집어버리자.(이 순서를 reversed bfs order, 루트 $r$에 대한 RBO라고 부르겠다) $S$와 $T$를 잇는 경로에서 $S$보다 깊은 점이 없거나 $T$보다 깊은 점이 없다. 따라서 0에 대한 RBO로 정점들에 대한 이분탐색을 해주면 양 끝 점 중 하나 $S$를 찾을 수 있다. 이제 루트를 $S$로 바꿔보면, $T$가 경로 상의 도시들 중 가장 깊은 도시가 되므로, S에 대한 RBO로 이분탐색을 다시 돌려주자.
5. 90점 풀이(Subtask 1, 2, 3, 4, 5, 6)
그래프에서는 $S$와 $T$를 잇는 가장 짧은 경로(여러 개 존재할 수 있다)에 속한 도시가 $S$와 $T$보다 깊어질 수 있다. 따라서 Subtask 4의 풀이를 적용할 수 없다. 도시들을 아무 순서대로 놓고 이분탐색을 한 번 진행하면 찾는 점은 $S$ 또는 $T$가 아닌 $S$와 $T$를 잇는 가장 짧은 경로의 어느 점이 될 수 있다.
하지만, $S$와 $T$ 사이의 존재하는 한 점을 알고 있다면? 그 도시를 $A$라고 하자. $A$를 기준으로 bfs tree를 만들면 subtask 4의 풀이를 적용해줄 수 있다. $A$에 대한 RBO로 이분탐색을 돌려 $S$를 찾고, $S$에 대한 RBO로 이분탐색을 돌려 $T$를 찾아주면 된다. 다른 최단경로가 있는지 없는지는 중요하지 않다. 다른 최단경로가 존재하는지 여부는 중요하지 않은데, 어차피 $S$ 주변의 도시를 혼잡하게 만들면 항상 통행료가 증가한다. 이때 $w_0$을 찾을 때 쿼리 1개, $A$, $S$, $T$를 찾을 때 쿼리 17개씩을 사용하므로 쿼리를 총 52개 사용해서 90점을 받을 수 있다. 쿼리 개수를 줄일 수 있는 갖가지 방법을 다 적용해서 쿼리 개수를 쥐어짜면 100점을 받을 수 있다. 반례가 있는지 없는지는 잘 모르겠다. 내 생각에는 반례가 없는 일종의 Case Work인 것 같다.
하지만 너무 추하니까 다른 풀이를 알아보자.
6. 100점 풀이(Subtask 1, 2, 3, 4, 5, 6)
Subtask 5에서 조금만 바꾸면 된다. 경로 상의 한 도시 $A$를 찾는 게 아니라 경로 상에서 연속한 두 도시 $U$, $V$를 알 수 있다. Subtask 1의 풀이를 데려와보자. 각 도로에 대해 모두 쿼리를 묻는 게 아니라, 도로들에 대해 이분탐색을 해주자. 단, 최단 경로가 여러 가지인 경우를 유의하자. 최단 경로가 여러 가지인 경우 때문에 간선들을 혼잡하게 만들 때 $l$부터 $mid$번 간선까지 혼잡하게 만드는 것이 아니라 $0$번부터 $mid$번 간선까지 혼잡하게 만들어야 한다.
$U$, $V$ 중 $S$는 $U$와 가깝고, $T$는 $V$와 더 가깝다. 그러면 모든 도시들을 $U$와 가까운 도시, $V$와 가까운 도시로 분류해줄 수 있다. $U$와 가까운 도시들의 그래프와 $V$와 가까운 도시들의 그래프는 연결그래프이기 때문에 여기서 다시 $U$에 대한 RBO로 이분탐색을 돌려 $S$를 찾고, $V$에 대한 RBO로 이분탐색을 돌려 $T$를 찾는다.
맨 처음에 1개의 쿼리, 간선을 찾을 때는 17개의 쿼리가 필요하다.
이때 $U$와 가까운 도시의 개수를 $x$라고 하면 $V$와 가까운 도시는 $N-x$개이다. 쿼리의 개수를 구해보자.
$\lceil\log_{2}(x) \rceil + \lceil\log_{2}(N-x)\rceil \leq \lfloor\log_{2}(x(N-x))\rfloor+2 \leq \lfloor \log_{2}(\frac{N^2}{4}) \rfloor + 2 \leq \lfloor 2\log_{2}(N) \rfloor = \lfloor 32.915... \rfloor = 32$
Q. 더 간단하게 할 수는 없나요?
A. 제 능지의 한계입니다. 부등식 남발하다 32 넘을 뻔했네요.
따라서 필요한 쿼리의 최대 개수는 1 + 17 + 32 = 50이고, 만점을 받을 수 있다.
<소스 코드>
#include "highway.h"
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
vector<pair<int, int>> adj[90002];
vector<int> w;
int M;
bool visited[90002];
int dist[90002][2];
vector<int> v[2];
int K = -1, S = -1, T = -1;
void bfs(int x, int N, int t)
{
for(int i=0;i<N;i++) visited[i] = false;
visited[x] = true;
queue<int> q;
q.push(x);
while(!q.empty())
{
int curr = q.front();
q.pop();
for(auto u : adj[curr])
{
int i = u.first, j = u.second;
if(!visited[i])
{
visited[i] = true;
q.push(i);
dist[i][t] = dist[curr][t] + 1;
}
}
}
}
void makezero()
{
for(int i=0;i<M;i++) w[i] = 0;
}
bool cmp(int x, int y)
{
return dist[x][0] > dist[y][0];
}
bool cmp2(int x, int y)
{
return dist[x][1] > dist[y][1];
}
void binarysearch(int l, int r, ll zerotoll, int k)
{
if(l == r)
{
if(k == 0) S = v[k][l];
if(k == 1) T = v[k][l];
return;
}
int mid = (l + r)/2;
makezero();
for(int i=l;i<=mid;i++)
{
for(auto u : adj[v[k][i]])
{
w[u.second] = 1;
}
}
ll toll = ask(w);
if(toll > zerotoll) binarysearch(l, mid, zerotoll, k);
else binarysearch(mid+1, r, zerotoll, k);
}
void find_pair(int N, std::vector<int> U, std::vector<int> V, int A, int B) {
M = U.size();
for(int j=0;j<M;j++) { adj[U[j]].push_back({V[j], j}); adj[V[j]].push_back({U[j], j}); }
w.resize(M);
makezero();
ll zerotoll = ask(w);
int l = 0, r = M-1;
while(l < r)
{
makezero();
int mid = (l + r)/2;
for(int i=0;i<=mid;i++)
{
w[i] = 1;
}
if(ask(w) > zerotoll) r = mid;
else l = mid+1;
}
bfs(U[l], N, 0);
bfs(V[l], N, 1);
for(int i=0;i<N;i++)
{
if(dist[i][0] < dist[i][1]) v[0].push_back(i);
else v[1].push_back(i);
}
sort(v[0].begin(), v[0].end(), cmp);
sort(v[1].begin(), v[1].end(), cmp2);
binarysearch(0, (int)v[0].size()-1, zerotoll, 0);
binarysearch(0, (int)v[1].size()-1, zerotoll, 1);
answer(S, T);
}
'올림피아드 문제 풀이' 카테고리의 다른 글
IOI 2018 4번) Mechanical Doll (2) | 2021.02.05 |
---|---|
2021.01.03 PS - CEOI 2017 2번 (0) | 2021.01.04 |