페이지

레이블이 priority queue인 게시물을 표시합니다. 모든 게시물 표시
레이블이 priority queue인 게시물을 표시합니다. 모든 게시물 표시

12736번: Fireworks

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

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;
}

14452번: Cow Dance Show

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


$O(n\lg^2n)$

최소 t는 n에 대한 단조 감소 함수이므로 t<=t_max를 만족하는지 묻는 결정문제로 바꿔 풀 수 있다. 현재 댄스가 가장 빨리 끝나는 소를 pop하고 새로운 소를 추가하며 우선순위 큐를 관리하면 해당 n에 대한 최소 t를 구할 수 있다.

#include<cstdio>
#include<queue>
using namespace std;
int t, n, a[100000];
bool f(int x) {
    priority_queue<int> pq;
    for (int i = 0; i < x; i++) pq.push(-a[i]);
    for (int i = x; i < n; i++) {
        pq.push(pq.top() - a[i]);
        pq.pop();
    }
    while (pq.size() - 1) pq.pop();
    return -pq.top() <= t;
}
int main() {
    scanf("%d%d", &n, &t);
    for (int i = 0; i < n; i++) scanf("%d", a + i);
    int low = 1, high = n, mid;
    while (low <= high) {
        mid = (low + high) / 2;
        f(mid) ? high = mid - 1 : low = mid + 1;
    }
    printf("%d", low);
    return 0;
}

1766번: 문제집

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


$O(m+n\lg n)$

위상정렬을 할 때, indegree가 0이면서 가장 작은 번호의 문제를 먼저 푼다.

#include<cstdio>
#include<queue>
#include<vector>
#include<functional>
using namespace std;
const int MXN = 32e3;
int n, m, x, y, ind[MXN + 1];
vector<int> adj[MXN + 1];
priority_queue<int, vector<int>, greater<int> > pq;
int main() {
    for (scanf("%d%d", &n, &m); m--;) {
        scanf("%d%d", &x, &y);
        adj[x].push_back(y);
        ind[y]++;
    }
    for (int i = 1; i <= n; i++) if (!ind[i]) pq.push(i);
    while (!pq.empty()) {
        int h = pq.top();
        pq.pop();
        printf("%d ", h);
        for (auto it : adj[h]) if (!--ind[it]) pq.push(it);
    }
    return 0;
}

2276번: 물 채우기

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


$O(nm\lg (nm))$

수면이 점점 올라가 잠긴다고 생각하자.
잠기는 구역이 새로 생길때 (수면의 높이)-(잠기는 구역 높이) 합을 누적하면 답을 구할 수 있다.
답을 빠르기 구하기 위해 우선순위 큐 pq를 사용하자.
pq는 (잠기기 시작하는 지점 위치, 높이 h) 정보를 가지고 있고 작은 높이의 원소가 top이 된다.
top의 지점에서 시작해서 아직까지 잠기지 않은 지점들 중 h보다 작거나 같은 지점들을  모두 체크해주고 h-(잠기는 구역 높이)를 누적한다.
새로 체크된 주변 지점들 중 체크되지 않은 지점들을 pq에 넣고 이를 반복한다.

#include<cstdio>
#include<queue>
using namespace std;
const int fx[] = { 1,0,-1,0 }, fy[] = { 0,1,0,-1 };
int n, m, ck[300][300], h[300][300], r;
struct st {
    int x, y;
    bool operator<(st i) const {
        return h[x][y] > h[i.x][i.y];
    }
};
priority_queue<st> pq;
void dfs(int x, int y, int z) {
    for (int i = 0; i < 4; i++) {
        int tx = x + fx[i], ty = y + fy[i];
        if (tx >= 0 && ty >= 0 && tx < m&&ty < n&&!ck[tx][ty]) {
            ck[tx][ty] = 1;
            if (h[tx][ty] > z) pq.push({ tx,ty });
            else {
                r += z - h[tx][ty];
                dfs(tx, ty, z);
            }
        }
    }
}
int main() {
    scanf("%d%d", &n, &m);
    for (int i = 0; i < m; i++) {
        for (int j = 0; j < n; j++) {
            scanf("%d", h[i] + j);
            if (i == 0 || j == 0 || i == m - 1 || j == n - 1) pq.push({ i,j }), ck[i][j] = 1;
        }
    }
    while (!pq.empty()) {
        st t = pq.top();
        pq.pop();
        dfs(t.x, t.y, h[t.x][t.y]);
    }
    printf("%d", r);
    return 0;
}

2109번: 순회강연

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


$O(nlgn)$

(di,pi)를 데드라인 기준으로 오름차순 정렬한 뒤 다음 그리디 알고리즘을 적용한다.
1. pi를 스케줄에 넣는다.
2. 현재 스캐줄이 불가능한 경우(즉, 일 개수>di) 스케줄 안에 p가 작은 일을 제거한다.

이 알고리즘의 정당성을 증명하기 위해 2번에 귀류법을 적용한다.
그 당시 제거한 이익을 px라 하자. px<=pi 일 것이다.
최종 스케줄 상에 pi 대신 px가 들어갔을 때 더 높은 수익을 얻을 수 있는 경우는 없으므로 증명 완료.


#include<cstdio>
#include<queue>
#include<algorithm>
using namespace std;
int n, r;
pair<int, int> p[10000];
priority_queue<int> pq;
int main() {
    scanf("%d", &n);
    for (int i = 0; i < n; i++) scanf("%d %d", &p[i].second, &p[i].first);
    sort(p, p + n);
    for (int i = 0; i < n; i++) {
        r += p[i].second;
        pq.push(-p[i].second);
        if (pq.size() > p[i].first) r += pq.top(), pq.pop();
    }
    printf("%d", r);
    return 0;
}

1826번: 연료 채우기

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


$O(n\lg n)$

현재까지 지나쳐온 주유소 중 가장 많은 연료를 가진 주유소를 들렸던 것으로 여기고 이를 도착할 때까지 반복한다.


#include<cstdio>
#include<queue>
#include<algorithm>
using namespace std;
int n, l, p, r;
pair<int, int> g[10000];
priority_queue<int> pq;
int main() {
    scanf("%d", &n);
    for (int i = 0; i < n; i++) scanf("%d %d", &g[i].first, &g[i].second);
    scanf("%d %d", &l, &p);
    sort(g, g + n);
    for (int i = 0; p<l; r++) {
        for (; i < n&&g[i].first <= p; i++) pq.push(g[i].second);
        if (pq.empty()) break;
        p += pq.top();
        pq.pop();
    }
    printf("%d", p < l ? -1 : r);
    return 0;
}

5461번: 고용

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


$O(n\lg n)$

먼저, 노동자들을 s/q를 기준으로 오름차순 정렬한다.
그러면 i번 노동자를 고용할 수 있을 경우 1...i-1번 노동자도 고용할 수 있다.
고용 가능한 노동자 후보가 있을 때 q가 작은 순서대로 고용할 때 가장 많은 노동자를 고용할 수 있다.
Ai를 1...i번 노동자 중 q가 작은 순서대로 최대한 많이 고용한 노동자의 집합이라 하자.
그러면 Ai에서 고용되지 않는 노동자는 Ai+1에서 역시 고용되지 못한다. 이는 귀류법으로 간단히 증명된다.
이러한 성질을 이용하여 노동자 집합을 q를 기준으로 하는 우선순위 큐를 통해 관리할 수 있다.


#include<cstdio>
#include<algorithm>
#include<queue>
using namespace std;
const int MXN = 5e5;
int n, q[MXN + 1], sz, ti;
pair<double, int> p[MXN + 1];
pair<int, int> r[MXN + 1];
long long w, s;
double tw;
priority_queue<pair<int, int> > pq;
int main() {
    scanf("%d%lld", &n, &w);
    for (int i = 1, x; i <= n; i++) {
        scanf("%d%d", &x, q + i);
        p[i] = { x*1.0 / q[i],i };
    }
    sort(p + 1, p + 1 + n);
    for (int i = 1; i <= n; i++) {
        s += q[p[i].second];
        pq.push(r[i] = { q[p[i].second],p[i].second });
        while (s*p[i].first > w) s -= pq.top().first, pq.pop();
        if (pq.size() > sz || pq.size() == sz&&s*p[i].first < tw) {
            sz = pq.size();
            tw = s*p[i].first;
            ti = i;
        }
    }
    sort(r + 1, r + 1 + ti);
    printf("%d\n", sz);
    for (int i = 1; i <= sz; i++) printf("%d\n", r[i].second);
    return 0;
}

10922번: 말

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

대회 당시 인터렉티브 문제로 출제되었으므로 입력을 미리 받아야 쓸 수 있는 풀이는 자제하자.


$O(n\lg n+m\lg (n+m)\lg X)$ // X는 X[i],Y[i]의 최댓값

X[i], Y[i]가 수정될 수 있을 때, f(x)=X[0]*X[1]*...*X[x]*Y[x]의 최댓값을 구하는 문제이다.
단순히 구간 갱신 쿼리를 관리하는 문제로 바꿔 풀면 누적되는 수가 너무 커질 수 있다.
X[i], Y[i]가 자연수이며 10억 이하인 점을 주목하자.
f(x)가 x=i일 때 최댓값을 가진다고 해보자.
그렇다면 X[j]>1 (j>i)를 만족하는 j는 30개 보다 작아야 한다.
그렇지 않으면,
f(n)/f(i) = X[i+1]*X[i+2]*...*X[n]*Y[n]/X[i] <= 1이어야 하는데
10^9 < 2^30 <= X[i+1]*X[i+2]*...*X[n] <= X[i]/Y[n] <= 10^9 이므로 모순이기 때문이다.

X[x]>1인 x를 내림차순 정렬한 수열을 {x_i}라 하자.(x_0=n, 예외로 이 수열의 마지막 항은 항상 0이다.)
X[1]*x[2]*...*X[i]*Y[k] (k는 x_i<=j<x_i-1일 때 최대 Y[j]를 만드는 j)
i<=30에 대한 k가 최대 f(x)를 만드는 x의 후보가 될 수 있다. xi는 우선순위 큐를 이용해 관리할 수 있을 것이다.

최댓값은 x[1]*x[2]*...*x[i]와 y[i...j]의  최댓값을 구하는 세그먼트 트리를 활용하여 구할 수 있다.


#include<cstdio>
#include<queue>
#include<algorithm>
#define mod 1000000007
using namespace std;
const int MXN = 5e5;
int n, m, x[MXN], tmul[MXN * 4], tmax[MXN * 4];
priority_queue<int> pq;
void upmax(int h, int l, int r, int g, int x) {
    if (r < g || g < l) return;
    if (l == r) {
        tmax[h] = x;
        return;
    }
    upmax(h * 2 + 1, l, (l + r) / 2, g, x);
    upmax(h * 2 + 2, (l + r) / 2 + 1, r, g, x);
    tmax[h] = max(tmax[h * 2 + 1], tmax[h * 2 + 2]);
}
int qmax(int h, int l, int r, int gl, int gr) {
    if (r < gl || gr < l) return 1;
    if (gl <= l&&r <= gr) return tmax[h];
    return max(qmax(h * 2 + 1, l, (l + r) / 2, gl, gr), qmax(h * 2 + 2, (l + r) / 2 + 1, r, gl, gr));
}
void upmul(int h, int l, int r, int g, int x) {
    if (r < g || g < l) return;
    if (l == r) {
        tmul[h] = x;
        return;
    }
    upmul(h * 2 + 1, l, (l + r) / 2, g, x);
    upmul(h * 2 + 2, (l + r) / 2 + 1, r, g, x);
    tmul[h] = (long long)tmul[h * 2 + 1] * tmul[h * 2 + 2] % mod;
}
int qmul(int h, int l, int r, int g) {
    if (g < l) return 1;
    if (r <= g) return tmul[h];
    return (long long)qmul(h * 2 + 1, l, (l + r) / 2, g)*qmul(h * 2 + 2, (l + r) / 2 + 1, r, g) % mod;
}
void res() {
    int last = n, use[30], ucnt = 0;
    long long maxi = 1;
    for (int i = 0; i < 30 && maxi <= 1e9; i++) {
        while (!pq.empty() && (x[pq.top()] == 1 || pq.top() == last)) pq.pop();
        if (pq.empty()) break;
        maxi = max(maxi, 1LL * qmax(0, 0, n - 1, pq.top(), last - 1));
        maxi *= x[pq.top()];
        use[ucnt++] = last = pq.top();
        pq.pop();
    }
    if (pq.empty()) maxi = max(maxi, 1LL * qmax(0, 0, n - 1, 0, last - 1)), last = 0;
    printf("%lld\n", maxi%mod*qmul(0, 0, n - 1, last - 1) % mod);
    while (ucnt--) pq.push(use[ucnt]);
}
int main() {
    scanf("%d", &n);
    for (int i = 0; i < n; i++) {
        scanf("%d", x + i);
        pq.push(i);
        upmul(0, 0, n - 1, i, x[i]);
    }
    for (int i = 0, y; i < n; i++) {
        scanf("%d", &y);
        upmax(0, 0, n - 1, i, y);
    }
    scanf("%d", &m);
    res();
    for (int i = 0, a, b, c; i < m; i++) {
        scanf("%d%d%d", &a, &b, &c);
        if (a == 1) {
            pq.push(b);
            upmul(0, 0, n - 1, b, x[b] = c);
        }
        else upmax(0, 0, n - 1, b, c);
        res();
    }
    return 0;
}

4002번: 닌자배치

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


$O(n{lgn}^2)$

i번 닌자를 매니저로 정할 때, i번 닌자의 자손들 중 예산 안에서 월급이 작은 닌자부터 차례대로 고용하는 것이 최선이다.
이 때 고용된 닌자들 집합을 Ai라 하자.
i번 닌자의 자식들을 sj라 하면
언제나 Asj들을 만들 때 제외된 닌자들은 Ai에 존재할 수 없다.(귀류법으로 증명할 수 있다.)
따라서 자식들의 Asj 합집합 + i번 닌자에서 예산이 넘치는 만큼 월급이 큰 닌자부터 제외시킨 집합이 Ai가 된다.
값이 큰 원소부터 제외하는 연산은 우선순위 큐로 구현할 수 있고, 이들을 합칠 때 사이즈가 큰 큐쪽으로 합치면 총 O(nlgn)의 연산횟수에 문제를 해결할 수 있다.

#include<cstdio>
#include<algorithm>
#include<queue>
#include<vector>
using namespace std;
typedef long long ll;
const int MXN = 1e5;
int n, m, p[MXN + 1];
ll c[MXN + 1], l[MXN + 1], r;
priority_queue<int> pq[MXN + 1];
vector<int> adj[MXN + 1];
void f(int h) {
    p[h] = h;
    pq[h].push(c[h]);
    for (auto it : adj[h]) {
        f(it);
        if (pq[p[h]].size() < pq[p[it]].size()) swap(p[h], p[it]);
        while (!pq[p[it]].empty()) pq[p[h]].push(pq[p[it]].top()), pq[p[it]].pop();
        c[h] += c[it];
    }
    while (c[h] > m) c[h] -= pq[p[h]].top(), pq[p[h]].pop();
    r = max(r, (ll)pq[p[h]].size()*l[h]);
}
int main() {
    scanf("%d%d", &n, &m);
    for (int i = 1, x; i <= n; i++) {
        scanf("%d%lld%lld", &x, c + i, l + i);
        adj[x].push_back(i);
    }
    f(1);
    printf("%lld", r);
    return 0;
}



$O(nlgn)$

처음 아이디어는 위와 같다.
트리를 순회하면서 번호를 매기면 어떤 정점의 자손들이 모두 인접한 번호를 가지고 있게 할 수 있다.
세그먼트 트리를 이용해 해당 구역에서 월급이 가장 큰 닌자를 O(lgn)에 찾고 제거할 수 있다.


#include<cstdio>
#include<vector>
#include<algorithm>
using namespace std;
typedef long long ll;
const int MXN = 1e5;
int n, m, l[MXN + 1], p[MXN + 1], cnt;
ll c[MXN + 1], r;
pair<int, int> tree[MXN * 4];
vector<int> adj[MXN + 1];
void insert(int h, int s, int e, int g, int x) {
    if (e < g || g < s) return;
    if (s == e) {
        tree[h] = { x,g };
        return;
    }
    insert(h * 2 + 1, s, (s + e) / 2, g, x);
    insert(h * 2 + 2, (s + e) / 2 + 1, e, g, x);
    tree[h] = max(tree[h * 2 + 1], tree[h * 2 + 2]);
}
pair<int, int> find(int h, int s, int e, int gs, int ge) {
    if (ge < s || e < gs) return{ 0,0 };
    if (gs <= s&&e <= ge) return tree[h];
    return max(find(h * 2 + 1, s, (s + e) / 2, gs, ge), find(h * 2 + 2, (s + e) / 2 + 1, e, gs, ge));
}
void f(int h) {
    int s = cnt;
    p[h]++;
    insert(0, 0, n - 1, cnt++, c[h]);
    for (auto it : adj[h]) {
        f(it);
        c[h] += c[it];
        p[h] += p[it];
    }
    while (c[h] > m) {
        pair<int, int> t = find(0, 0, n - 1, s, cnt - 1);
        insert(0, 0, n - 1, t.second, 0);
        c[h] -= t.first;
        p[h]--;
    }
    r = max(r, (ll)p[h] * l[h]);
}
int main() {
    scanf("%d%d", &n, &m);
    for (int i = 1, x; i <= n; i++) {
        scanf("%d%lld%d", &x, c + i, l + i);
        adj[x].push_back(i);
    }
    f(1);
    printf("%lld", r);
    return 0;
}

2696번: 중앙값 구하기

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


$O(tn\lg n)$

두 개의 우선순위 큐 l,r을 준비한다.
a[1...2k-1]의 중앙값을 구하고자 할 때,
a[1...2k-1]을 정렬한 배열을 b[1...2k-1]이라 하면 l은 b[1...k], r은 b[k+1...2k-1]을 가지도록 유지한다.
a[1...2k-1]까지 처리되었고 두 수 x=a[2k], y=a[2k+1]가 추가되었다고 하자.
먼저, l에 x를 추가하고 r에 y를 추가한다.
그리고 l의 최댓값(X)과 r의 최솟값(Y)을 비교하여 X>Y인 경우 두 수를 각각 처음과 다른 우선순위 큐로 옮긴다.
이렇게 하면 위 조건에 맞게 l, r을 유지할 수 있다.


#include<cstdio>
#include<queue>
#include<algorithm>
using namespace std;
int t, m, x, y;
int main() {
    for (scanf("%d", &t); t--;) {
        priority_queue<int> l, r;
        scanf("%d%d", &m, &x);
        l.push(x);
        printf("%d\n%d ", m / 2 + 1, x);
        for (int i = 1, x, y; i <= m / 2; i++) {
            scanf("%d%d", &x, &y);
            l.push(x);
            r.push(-y);
            if ((x = l.top())>(y = -r.top())) {
                l.pop(); l.push(y);
                r.pop(); r.push(-x);
            }
            if (i % 10 == 0) puts("");
            printf("%d ", l.top());
        }
        puts("");
    }
    return 0;
}

11286번: 절대값 힙

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


$O(n\lg n)$

우선순위 큐를 이용한다.


#include<cstdio>
#include<algorithm>
#include<queue>
using namespace std;
priority_queue<pair<int, int> > pq;
int n, x;
int main() {
    for (scanf("%d", &n); n--;) {
        scanf("%d", &x);
        if (x) pq.push({ -abs(x),-x });
        else if (pq.empty()) puts("0");
        else printf("%d\n", -pq.top().second), pq.pop();
    }
    return 0;
}

11279번: 최대 힙

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


$O(n\lg n)$


#include<cstdio>
#include<queue>
using namespace std;
priority_queue<int> pq;
int n, x;
int main() {
    for (scanf("%d", &n); n--;) {
        scanf("%d", &x);
        if (x) pq.push(x);
        else if (pq.empty()) puts("0");
        else printf("%d\n", pq.top()), pq.pop();
    }
    return 0;
}

1202번: 보석 도둑

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


$O(n\lg n+k\lg {nk})$

큰 가방은 작은 가방이 넣을 수 있는 보석은 모두 넣을 수 있기 때문에 작은 가방부터 넣을 수 있는 최대 가치의 보석을 넣으면 된다.


#include<cstdio>
#include<queue>
#include<algorithm>
using namespace std;
const int MAX_N = 3e5;
typedef long long ll;
int n, k, c[MAX_N];
ll r;
pair<int, int> p[MAX_N];
priority_queue<int> pq;
int main() {
    scanf("%d %d", &n, &k);
    for (int i = 0; i < n; i++) scanf("%d %d", &p[i].first, &p[i].second);
    for (int i = 0; i < k; i++) scanf("%d", c + i);
    sort(p, p + n);
    sort(c, c + k);
    for (int i = 0, j = 0; i < k; i++) {
        while (j < n && p[j].first <= c[i]) pq.push(p[j++].second);
        if (!pq.empty()) {
            r += pq.top();
            pq.pop();
        }
    }
    printf("%lld", r);
    return 0;
}