2018년 9월 2일 일요일

SW Expert Academy - 1차 문제

1858 모범 택시 드라이버 [D6]
3421 수제 버거 장인 [D5]
3503 초보자를 위한 점프대 배치하기 [D5]
4747 사막에서 만난 지니 [D6]
3082 화면 보호기 [D7] https://www.acmicpc.net/problem/3990

1. 1858 모범 택시 드라이버 
N개의 도시와 M개의 도로가 있다. 
각 도로는 일방통행이며 제한속도 V와 도로의 길이 L이 주어진다.
V가 0인경우에는 이전속도를 유지하며 달린다.
0번도시에서 70의 속도로 출발할 때 D까지 도달하는 최단 시간 경로를 구하는 문제이다.
============================================================================

도로를 이동할 때는 제한속도 V만큼의 속도로 가는것이 가장 최단시간에 갈 수 있는것을 알 수 있다.
각 도시를 도달할때의 속도가 다르고 시간또한 다르기 때문에 정점을 N*V개 놓고 다익스트라를 
돌리면 된다.
#pragma GCC optimize ("O3")
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 155;
const int MAXM = 505;
#define INF (987654321)
int T, n, m, d;
vector<pair<int, pair<int, int>>> adj[MAXN];        //e, v, l
double dist[MAXN][MAXM];
pair<int,int> par[MAXN][MAXM];
void dijkstra() {
    for (int i = 0; i < n; i++) for (int j = 0; j < MAXM; j++) dist[i][j] = INF;
    priority_queue<pair<double, pair<int, int>>> pq;
    
    dist[0][70] = 0;
    pq.push({ 0,{70, 0} });
    while (!pq.empty()) {
        double t = -pq.top().first;
        int v = pq.top().second.first;
        int here = pq.top().second.second;
        pq.pop();
        if (here == d) return;
        for (pair<int, pair<int, int>> x : adj[here]) {
            int nxt = x.first;
            int limit = x.second.first;
            int len = x.second.second;
            if (limit == 0) limit = v;
            double cost = len / (double)limit;
            if (dist[nxt][limit] > t + cost) {
                dist[nxt][limit] = t + cost;
                pq.push({ -dist[nxt][limit], {limit, nxt} });
                par[nxt][limit] = { here, v };
            }
        }
    }
}
int main() {
    scanf("%d", &T);
    for (int t = 1; t <= T; t++) {
        scanf("%d%d%d", &n, &m, &d);
        for (int i = 0; i < MAXN; i++) adj[i].clear();
        for (int i = 0; i < m; i++) {
            int s, e, v, l;
            scanf("%d%d%d%d", &s, &e, &v, &l);
            adj[s].push_back({ e,{v,l} });
        }
        dijkstra();
        double v = INF;
        int st = -1;
        for (int i = 1; i < MAXM; i++) {
            if (dist[d][i] < v) {
                v = dist[d][i];
                st = i;
            }
        }
        stack<int> stk;
        for (pair<int, int> x = { d, st };; x = par[x.first][x.second]) {
            stk.push(x.first);
            if (x.first == 0 && x.second == 70) break;
        }
        printf("#%d ", t);
        while (!stk.empty()) printf("%d ", stk.top()), stk.pop();
        puts("");
    }
    return 0;
}
cs

2. 3421 수제 버거 장인
N개의 재료가 있고 M개의 궁합이 맞지않는 쌍이 주어진다.
N개의 재료들로 만들 수 있는 조합의 개수를 구하는 문제이다.
============================================================================

처음에는 $O(2^NM)$의 복잡도로 제출했는데 통과가 되긴하였다. 
사실 $O(2^N)$풀이가 존재한다.

각각의 재료마다 궁합이 맞지않는 다른 재료들을 bit mask형태로 저장해 놓는다.
$2^N$의 경우를 보며 현재 재료를 넣어도 되는 경우에만 넣으면 된다.
#pragma GCC optimize ("O3") 
#include <bits/stdc++.h>
using namespace std;
int T, n, m, cnt;
int no[20];
void solve(int idx, int info) {
    if (idx == n) {
        cnt++;
        return;
    }
    if (!(no[idx] & info)) solve(idx + 1, info | (1 << idx));
    solve(idx + 1, info);
}
int main() {
    scanf("%d", &T);
    for (int t = 1; t <= T; t++) {
        cnt = 0;
        scanf("%d%d", &n, &m);
        for (int i = 0; i < n; i++) no[i] = 0;
        for (int i = 0; i < m; i++) {
            int u, v;
            scanf("%d%d", &u, &v);
            u--; v--;
            no[u] |= (1 << v);
            no[v] |= (1 << u);
        }
        solve(0, 0); 
        printf("#%d %d\n", t, cnt);
    }
    return 0;
}
cs

3. 3503 초보자를 위한 점프대 배치하기
N개의 막대를 재배치하여 두 막대의 최대 높이차가 최소화하는 문제이다.
============================================================================


직관적으로 정렬을 시키고 가우시안 분포처럼 막대를 배치하는것이 최적일거 같은 느낌이든다.
실제로도 답이고 정확한 증명은 좀더 고민을 해봐야 겠다.

doju님의 증명 힌트 : 
그 직관적으로 나오는 답에서 간격 상한을 더 줄였을 때 사이클이 생길 수 없음을
증명하는 방향으로 시도해 보세요.

#pragma GCC optimize ("O3") 
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 1e5 + 5;
int T, n, arr[MAXN];
int tmp[MAXN];
int main() {
    scanf("%d", &T);
    for (int t = 1; t <= T; t++) {
        scanf("%d", &n);
        for (int i = 0; i < n; i++) scanf("%d", &arr[i]);
        sort(arr, arr + n);
        int l = 0, r = n - 1;
        for (int i = 0; i < n; i++) {
            if (~i & 1) tmp[l++] = arr[i];
            else tmp[r--] = arr[i];
        }
        int ans = abs(tmp[0] - tmp[n - 1]);
        for (int i = 0; i < n - 1; i++) ans = max(ans, abs(tmp[i] - tmp[i + 1]));
        printf("#%d %d\n", t, ans);
    }
    return 0;
}
cs

4. 4747 사막에서 만난 지니
N개의 수열이 주어졌을 때 각각의 합이 같도록 3가지로 분류하는 방법을 구하는 문제이다.
즉, [1, 4, 2, 3, 2]가 주어진다면 [1, 3], [2, 2], [4]로 분류할 수 있다.
또한 수열의 값들은 균등하게 분포되있다.
============================================================================

N이 사실상 2,000이여서 마땅한 방법을 생각해내기 어렵다.
푸는 방법도 다양한 방법이 존재할것 같은 문제다.

우선 각각의 분류에는 (수열의 합 / 3)만큼이 할당되어야 할 것이다.
나는 각각의 분류를 동시에 채우는 것이 아니라 하나의 분류를 채운 후에 
나머지 분류들을 채우는 방식으로 구현하였다.
채울 때에는 큰 수부터 작은수를 넣고 채우는것이 불가능하면 back-tracking하여 
다시 탐색을 진행한다.
시간 복잡도는 $O(3^N)$이다. 수열의 값이 균등하여 최악의 시간내에는 답이 나온다.
#pragma GCC optimize ("O3") 
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 2005;
int T, n;
int arr[MAXN];
bool visit[MAXN];
int s;
vector<int> ans[3];
bool f;
void solve(int xdi, int idx, int sum) {
    if (xdi == 2) {
        f = true;
        return;
    }
    if (sum == s) {
        solve(xdi + 1, n - 1, 0);
        return;
    }
    if (idx < 0) return;
    if (visit[idx]) {
        solve(xdi, idx - 1, sum);
        return;
    }
    if (f) return;
    if (sum + arr[idx] <= s) {
        visit[idx] = true;
        ans[xdi].push_back(arr[idx]);
        solve(xdi, idx - 1, sum + arr[idx]);
        if (f) return;
        ans[xdi].pop_back();
        visit[idx] = false;
    }
    if (f) return;
    solve(xdi, idx - 1, sum);
}
int main() {
    scanf("%d", &T);
    for (int t = 1; t <= T; t++) {
        memset(visit, 0, sizeof visit);
        f = false;
        scanf("%d", &n);
        s = 0;
        for (int i = 0; i < 3; i++) ans[i].clear();
        for (int i = 0; i < n; i++) scanf("%d", &arr[i]), s += arr[i];
        s /= 3;
        solve(0, n - 1, 0);
        printf("#%d\n", t);
        for (int i = 0; i < 2; i++) {
            for (int j = 0; j < ans[i].size(); j++) printf("%d ", ans[i][j]);
            puts("");
        }
        for (int i = 0; i < n; i++) if (!visit[i]) printf("%d ", arr[i]);
        puts("");
    }
    return 0;
}
cs

5. 3082 화면 보호기 

풀지 못했다. 좀더 고민해봐야겠다 ㅠㅠ

2018년 7월 28일 토요일

10534 락페스티벌

10534 락페스티벌 https://www.acmicpc.net/problem/10534


직사각형 N개가 주어질 때 모서리가 인접한 직사각형은 같은 그룹이다.
가장 넓은 그룹의 넓이를 구하는 문제이다.
============================================================================




직사각형 한 개당 너비와 높이가 최대 500이므로 직사각형 각각의 grid를 이용해서 풀기는 어렵다.
직사각형의 선분을 이용해보자!

모든 직사각형의 x,y좌표들을 압축을 해주고 y좌표에는 직사각형의 너비를, x좌표에는 직사각형의 
높이를 넣어준다.

선분이 교차하는 부분이 있다면 같은 그룹이다.
선분이 교차하는것은 sweeping을 하면서 판별해 주고 union find로 같은그룹으로 묶어주면 해결된다.
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 5e4 + 5;
typedef long long ll;
struct Rect {
    int x, y, w, h;
};
int n;
bool visit[MAXN];
Rect rect[MAXN];
int par[MAXN];
ll sze[MAXN];
int find(int x) {
    if (x == par[x]) return x;
    return par[x] = find(par[x]);
}
void merge(int u, int v) {
    u = find(u); v = find(v);
    if (u == v) return;
    if (sze[u] > sze[v]) swap(u, v);
    sze[v] += sze[u];
    sze[u] = 0;
    par[u] = v;
}
vector<ll> xpos, ypos;
set<pair<ll,pair<ll, int>>> savex[100005], savey[100005];
int main() {
    scanf("%d", &n);
    for (int i = 1; i <= n; i++) {
        scanf("%d%d%d%d", &rect[i].x, &rect[i].y, &rect[i].w, &rect[i].h);
        sze[i] = rect[i].w*rect[i].h;
        par[i] = i;
        xpos.push_back(rect[i].x);
        xpos.push_back((ll)rect[i].x + (ll)rect[i].w);
        ypos.push_back(rect[i].y);
        ypos.push_back((ll)rect[i].y + (ll)rect[i].h);
    }
    sort(xpos.begin(), xpos.end());
    sort(ypos.begin(), ypos.end());
    xpos.erase(unique(xpos.begin(), xpos.end()), xpos.end());
    ypos.erase(unique(ypos.begin(), ypos.end()), ypos.end());
    for (int i = 1; i <= n; i++) {
        int idx = lower_bound(xpos.begin(), xpos.end(), rect[i].x) - xpos.begin();
        savex[idx].insert({ rect[i].y, {-rect[i].h, i} });
        idx = lower_bound(xpos.begin(), xpos.end(), (ll)rect[i].x + (ll)rect[i].w) - xpos.begin();
        savex[idx].insert({ rect[i].y, {-rect[i].h, i} });
        idx = lower_bound(ypos.begin(), ypos.end(), rect[i].y) - ypos.begin();
        savey[idx].insert({ rect[i].x, {-rect[i].w, i} });
        idx = lower_bound(ypos.begin(), ypos.end(), (ll)rect[i].y + (ll)rect[i].h) - ypos.begin();
        savey[idx].insert({ rect[i].x, {-rect[i].w ,i} });
    }
    for (int i = 0; i < xpos.size(); i++) {
        auto curr = savex[i].begin();
        auto nxt = curr;
        ll cover = (*curr).first - (*curr).second.first;
        while (++nxt != savex[i].end()) {
            int idx = (*curr).second.second, xdi = (*nxt).second.second;
            if ((*nxt).first <= cover) {
                merge(idx, xdi);
            }
            cover = max(cover, (*nxt).first - (*nxt).second.first);
            curr = nxt;
        }
    }
    for (int i = 0; i < ypos.size(); i++) {
        auto curr = savey[i].begin();
        auto nxt = curr;
        ll cover = (*curr).first - (*curr).second.first;
        while (++nxt != savey[i].end()) {
            int idx = (*curr).second.second, xdi = (*nxt).second.second;
            if ((*nxt).first <= cover) {
                merge(idx, xdi);
            }
            cover = max(cover, (*nxt).first - (*nxt).second.first);
            curr = nxt;
        }
    }
    ll ans = 0;
    for (int i = 1; i <= n; i++) ans = max(ans, sze[find(i)]);
    printf("%lld\n", ans);
    return 0;
}

cs

2018년 6월 27일 수요일

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

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

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

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

union find로 온라인에 해결할 수 있다.
x의 root까지의 상대적 무게 차이를 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, 0, sizeof 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