2018년 6월 27일 수요일

3830 교수님은 기다리지 않는다

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

ab의 상대적인 무게의 차이가 주어진다.
각각의 쿼리에 대해서 cd의 차이를 알 수 있는지 판별하고 그 차이를 구하는 문제이다.

처음에는 오프라인 알고리즘을 생각해봤다.
쿼리를 먼저 입력받고 그래프를 구성해서 sparse table을 이용해 $O(nlogn)$에 풀어봤는데 계속 TLE가 났다.

union find로 온라인에 해결할 수 있다.
xroot까지의 상대적 무게 차이를 dist[x]라 한다면 아래그림과 같이 나타낼 수 있다.





#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAXN = 1e5 + 5;
int n, m;
char type;
int a, b, c;
int par[MAXN], sze[MAXN];
ll dist[MAXN];
pair<int, ll> find(int x, ll ret) {
    if (x == par[x]) return { x, ret };
    return find(par[x], ret + dist[x]);
}
void merge(int a, int b, int t) {
    auto x = find(a, 0), y = find(b, 0);
    if (x.first == y.first) return;
    if (sze[x.first] > sze[y.first]) {
        dist[y.first] -= (y.second - x.second - t);
        par[y.first] = x.first;
        sze[x.first] += sze[y.first];
        sze[y.first] = 1;
    }
    else {
        dist[x.first] += (y.second - x.second - t);
        par[x.first] = y.first;    
        sze[y.first] += sze[x.first];
        sze[x.first] = 1;
    }
}
int main() {
    while (scanf("%d%d"&n, &m), !(n == 0 && m == 0)) {
        memset(dist, 0sizeof dist);
        for (int i = 1; i <= n; i++) par[i] = i, sze[i] = 1;
        for (int i = 0; i < m; i++) {
            scanf(" %c"&type);
            if (type == '!') {
                scanf("%d%d%d"&a, &b, &c);
                merge(a, b, c);
            }
            else {
                scanf("%d%d"&a, &b);
                auto aa = find(a, 0);
                auto bb = find(b, 0);
                if (aa.first != bb.first) {
                    printf("UNKNOWN\n");
                }
                else {
                    printf("%lld\n"-aa.second + bb.second);
                }
            }
        }
    }
    return 0;
}
cs

find함수를 다음과 같이 나타낼 수 있다.
이렇게 하면 root까지의 거리 dist[x]를 갱신하면서 root또한 갱신할 수 있다.
int find(int x) {
    if (x == par[x]) return x;
    int prv = find(par[x]);
    dist[x] += dist[par[x]];
    return par[x] = prv;
}
cs

2018년 6월 6일 수요일

15708 미네크래프트

15708 미네크래프트 https://www.acmicpc.net/problem/15708
Codeforces #470 (Div.2) C - Producing Snow http://codeforces.com/contest/948/problem/C
14452 Cow Dance Show https://www.acmicpc.net/problem/14452
1781 컵라면 https://www.acmicpc.net/problem/1781   (https://lyzqm.blogspot.com/2017/10/1781.html)

pqgreedy를 이용한 문제들을 소개하고자 한다.

1. 미네크래프트
시간 TN개의 바위를 각각 캐는데 걸리는 시간이 주어지고 옆의 바위로 건너가는 시간P가 주어진다.
캘 수 있는 바위의 최대개수를 구하는 문제이다.

현재 보고있는 바위를 캘지 정하는 것으로 풀지 말고 
지금 까지 봤던 바위들 중 작은 바위들을 캐서 현재 보고있는 바위까지 건너올 수 있게끔 풀어야 한다.

이러한 유형의 문제들은 우선적으로 pq에 넣어서 처리해주는 편이 좋다.
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 1e5 + 5;
int n, t, p;
int arr[MAXN];
priority_queue<int> pq;
int main() {
    scanf("%d%d%d"&n, &t, &p);
    for (int i = 0; i < n; i++scanf("%d"&arr[i]);
    int sum = 0, sze = 0, ans = 0;
    for (int i = 0; i < n; i++) {
        if (t <= p * i) break;
        sze++;
        sum += arr[i];
        pq.push(arr[i]);
        while (!pq.empty() && sum > t) {
            sum -= pq.top();
            sze--;
            pq.pop();
        }
        if (sum > t) break;
        ans = max(ans, sze);
        sum += p;
    }
    printf("%d\n", ans);
    return 0;
}
cs

2. Producing Snow 
N일 동안 눈이 $s_{i}$만큼 쌓인다.
각각의 날에 기온은 $t_{i}$이다.
눈이 녹는 정도는 그날 기온에 영향을 받으며 이전에 남았던 눈 또한 녹는다.
각각의 날에 녹는 눈의 양을 구하는 문제이다.


대회때는 못풀었던 아쉬운 문제이다.
pq에 $s_{i}$와 이전에 기온들의 합을 넣어 관리할 수 있다.
눈이 완전히 녹는것은 이전 기온들의 합과 현재 기온의 합보다 작거나 같아지는 경우이다.
아직 pq에 남아있는 크기만큼 눈들 또한 더 녹기 때문에 이것도 더해줘야한다.
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int n;
const int MAXN = 1e5 + 5;
int arr[MAXN];
priority_queue<ll> pq;
int main() {
    scanf("%d"&n);
    for (int i = 0; i < n; i++scanf("%d"&arr[i]);
    ll s = 0;
    for (int i = 0, minus; i < n; i++) {
        ll ret = 0;
        scanf("%d"&minus);
        pq.push(-(arr[i] + s));
        s += minus;
        while (!pq.empty() && -pq.top() <= s) {
            ret += -pq.top() - s + minus;
            pq.pop();
        }
        ret += pq.size() * minus;
        printf("%lld ", ret);
    }
    return 0;
}
cs

3. Cow Dance Snow
소들이 춤추는 시간들이 정해져있고 춤을 다 춘 소는 순차적으로 들어간다.
모든 소가 춤을 추는데 걸리는 시간 T를 넘지 않도록 소들이 춤출 수 있는 
최소 사이즈 K를 구하는 문제다. 

이분탐색과 위의 문제처럼 pq를 이용한 기법으로 해결할 수 있다.
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAXN = 1e6 + 6;
int n, t;
ll arr[MAXN];
bool ispossible(int idx) {
    ll prv = 0, sum = 0;
    priority_queue<ll> pq;
    for (int i = 0; i < idx; i++) pq.push(-arr[i]);
    while (!pq.empty()) {
        ll curr = -pq.top();
        pq.pop();
        sum += (curr - prv);
        if (sum > t) return false;
        prv = curr;
        if (idx < n) pq.push(-sum - arr[idx++]);
    }
    return sum <= t;
}
int main() {
    scanf("%d%d"&n, &t);
    for (int i = 0; i < n; i++scanf("%lld"&arr[i]);
    
    int l = 1, r = n;
    while (l <= r) {
        int mid = (l + r) >> 1;
        if (ispossible(mid)) r = mid - 1;
        else l = mid + 1;
    }
    printf("%d\n", l);
    return 0;
}
cs

2018년 6월 1일 금요일

15790 최종병기 활

15790 최종병기 활 https://www.acmicpc.net/problem/15790

고무줄이 주어진다. 고무줄의 둘레는 $n$이며 $m$개의 홈이 파져있다. 
고무줄을 잘라 $k$겹의 고무줄 중 가장 작은 고무줄의 길이를 최대화하는 문제이다.

순수 greedy만 생각을 했었는데 이분탐색으로 원의 기준을 돌리면서 $O(m^2 log n)$에 해결할 수 있다.

원의 기준을 돌리면서 arr배열에 다시 넣었는데 배열 크기를 2$m$으로 잡고 idx값만 바꾸면서 돌려줄 수 있다. 
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 1005;
int n, m, k;
int arr[MAXN];
int sub[MAXN];
bool ispossible(int mid) {        // 모든 part >= mid인지 판별
    for (int i = 0; i < m; i++) {
        int t = 0;
        int idx = 0;
        for (int j = i; j < m; j++) arr[idx++= sub[j];
        for (int j = 0; j < i; j++) arr[idx++= sub[j];
        int here = 0;
        for (int j = 0; j < m; j++) {
            if (here < mid) here += arr[j];
            if (here >= mid) {
                here = 0;
                t++;
            }
        }
        if (here >= mid) t++;
        if (t >= k) return true;
    }
    return false;
}
int main() {
    scanf("%d%d%d"&n, &m, &k);
    for (int i = 0; i < m; i++scanf("%d"&arr[i]);
    for (int i = 0; i < m - 1; i++) sub[i] = arr[i + 1- arr[i]; sub[m - 1= n - arr[m - 1+ arr[0];
    int l = 1, r = n;
    while (l <= r) {
        int mid = (l + r) >> 1;
        if (ispossible(mid)) l = mid + 1;
        else r = mid - 1;
    }
    printf("%d\n", r);
    return 0;
}
cs