i번 연결점에 이어진 폭약들을 시각 t에 동시에 떠뜨리기 위한 최소 비용을 f_i(t)라고 하자. 이 함수는 t에 관한 아래로 볼록인 함수이다.
또한 이 함수는 i번 연결점 바로 다음에 이어진 a0, a1, ..., aj 번 연결점에 관한 최소 비용 함수로부터 구할 수 있다.
(그리 어렵지 않으니 함수가 어떻게 구해지는지 한 번 생각보자.)
이 과정에서 함수끼리의 덧셈 및 볼록껍질의 제약적인 수정이 요구된다.
볼록 함수 끼리의 연산을 효율적으로 하기 위해 볼록 함수를 y절편, 기울기가 1씩 증가하는 t를 우선순위 큐에 저장해놓는다.
- 두 함수를 더하기 위해서는 y절편끼리 더하고 사이즈가 큰 우선순위 큐에 작은 우선순위 큐의 원소들을 모두 빼서 넣어주면 된다. ... (*)
- 볼록 껍질에서 기울기가 1보다 큰 부분을 없애야 하는 경우가 있다. 이 경우엔 간단히 우선순위 큐에서 pop 해주면 된다.
- 볼록 껍질에서 기울기가 -1인 부분을 늘려야 하는 경우가 있다. 이 경우엔 y절편과 기울기가 0인 부분을 잘 수정해주면 된다.
(*) 부분의 시간복잡도를 계산해보자.
먼저, 각 f_i(t)를 이루는 점의 수(곧, 우선순위 큐의 원소 개수)는 i번 연결점에 연결된 폭약의 수에 비례한다. 우선순위 큐 내부의 폭약에 관한 원소들은 (*) 부분에서 이동이 발생하고 그 때마다 자신이 속한 우선순위 큐의 사이즈가 2배이상으로 증가한다. 따라서 각 원소마다 이동횟수가 O(\lg n)로 제한되고 이동시 O(\lg n)의 시간이 걸리므로 총 시간복잡도는 O(n\lg^2 n).
최종 시간복잡도는 O(n\lg^2 n)
#include<cstdio> #include<queue> using namespace std; int n, m, p[300001], c[300001], sz[300001]; long long s[300001], l, r; priority_queue<long long> *pq[300001]; int main() { scanf("%d%d", &n, &m); for (int i = 2; i <= n + m; i++) scanf("%d%d", p + i, c + i); for (int i = n + 1; i <= n + m; i++) pq[i] = new priority_queue<long long>(2, 0); for (int i = n + m; i; i--) { while (sz[i]--) pq[i]->pop(); r = pq[i]->top(); pq[i]->pop(); l = pq[i]->top(); pq[i]->pop(); pq[i]->push(l + c[i]); pq[i]->push(r + c[i]); s[p[i]] += s[i] + c[i]; if (!pq[p[i]]) { pq[p[i]] = pq[i]; continue; } if (pq[p[i]]->size() < pq[i]->size()) swap(pq[p[i]], pq[i]); while (!pq[i]->empty()) pq[p[i]]->push(pq[i]->top()), pq[i]->pop(); sz[p[i]]++; } pq[0]->pop(); while (!pq[0]->empty()) s[0] -= pq[0]->top(), pq[0]->pop(); printf("%lld", s[0]); return 0; }
댓글 없음 :
댓글 쓰기