2017년 8월 31일 목요일

10803 정사각형 만들기

10803 정사각형 만들기 https://www.acmicpc.net/problem/10803
10805 L 모양의 종이 자르기 https://www.acmicpc.net/problem/10805

1. 정사각형 만들기
직사각형이 주어진다.
이 사각형으로 만들 수 있는 최대 정사각형 조각의 개수를 구하는 문제이다.

쉬운 DP구나 하고 풀었다가 TLE가 났다.
적당한 크기이상일 경우 최적화를 시켜 시간을 줄여줄 수 있다.
#include <cstdio>
#include <cstring>
#define INF 987654321
inline int min(int a, int b) { return a < b ? a : b; }
int N, M;
int dp[10001][101];
int solve(int x, int y) {
    if (x == y) return dp[x][y] = 1;
    if (x < 0 || y < 0return INF;
    int &ret = dp[x][y];
    if (ret != -1return ret;
    ret = x*y;
    // 
    if (x >= 4 * y) ret = min(ret, solve(x - y, y) + 1);
    else {
        for (int ny = 1;ny <= (y + 1/ 2;ny++)
            ret = min(ret, solve(x, ny) + solve(x, y - ny));
        for (int nx = 1; nx <= (x + 1/ 2;nx++)
            ret = min(ret, solve(nx, y) + solve(x - nx, y));
    }
    return ret;
}
int main() {
    memset(dp, -1sizeof dp);
    scanf("%d%d"&N, &M);
    printf("%d\n", solve(N, M));
    return 0;
}
cs
2. L 모양의 종이 자르기 
L모양의 종이를 자를때 정사각형 조각의 최소개수를 구하는 문제이다.

가로, 세로로 잘랐을 때의 경우를 생각하면 L모양 조각이 나오는 경우와 직사각형이 나오는 경우가 있다.
이 경우들을 이용해 정사각형 조각의 개수를 구하면 된다.
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define INF 0x7fffffff
int rdp[51][51];
int dp[51][51][51][51];
int rect(int a, int b) {
    if (a == 0 || b == 0return INF;
    if (a == b) return 1;
    if (a == 1return b;
    if (b == 1return a;
    int &ret = rdp[a][b];
    if (ret != -1return ret;
    ret = INF;
    for (int y = 1;y < a;y++) ret = min(ret, rect(y, b) + rect(a - y, b));
    for (int x = 1;x < b;x++) ret = min(ret, rect(a, x) + rect(a, b - x));
    return ret;
}
int solve(int a, int b, int c, int d) {
    int &ret = dp[a][b][c][d];
    if (ret != -1return ret;
    ret = INF;
    int u = b - d, v = a - c;
    for (int y = 1;y < a;y++) {
        if (y < c) ret = min(ret, rect(y, u) + solve(a - y, b, c - y, d));
        else if (y == c) ret = min(ret, rect(y, u) + rect(a - y, b));
        else ret = min(ret, solve(y, b, c, d) + rect(a - y, b));
    }
    for (int x = 1;x < b;x++) {
        if (x < u) ret = min(ret, rect(a, x) + solve(a, b - x, c, d));
        else if (x == u) ret = min(ret, rect(a, x) + rect(v, d));
        else ret = min(ret, solve(a, x, c, x - u) + rect(v, b - x));
    }
    return ret;
}
int main() {
    memset(rdp, -1sizeof rdp);
    memset(dp, -1sizeof dp);
    int a, b, c, d;
    scanf("%d%d%d%d"&a, &b, &c, &d);
    printf("%d\n", solve(a, b, c, d));
    return 0;
}
cs

1315 RPG

1315 RPG https://www.acmicpc.net/problem/1315

캐릭터는 $CharStr, CharInt$ 두 가지 스탯을 가지고 있다.
퀘스트가 N개 존재하는데 $(QuestStr <= CharStr) || (QuestInt <= CharInt)$이면 
퀘스트를 깨서 포인트를 얻을 수 있다.
포인트는 마음대로 스탯을 올릴 수 있다.
최대로 깰 수 있는 퀘스트의 개수를 구하는 문제이다.

스탯을 기준으로 2차원 dp를 잡으면 해결할 수 있다.
$Str$과 $Int$가 주어졌을 때 깰 수 있는 퀘스트는 정해져있으므로 
깨지 않은 퀘스트들에 대하여 포인트들의 합을 구하였다.

그 포인트들의 합으로 다음 스탯을 구해서 재귀로 돌렸다.
(다음 스탯을 구하는 부분에서 잘못생각해서 3번이나 틀렸다.)
#include <cstdio>
#include <cstring>
#include <vector>
#include <algorithm>
using namespace std;
struct Quest {
    int s, i, point;
};
int N;
Quest quest[101];
bool visit[101];
int dp[1001][1001];
int solve(int Str, int Int) {
    int &ret = dp[Str][Int];
    if (ret != -1return ret;
    ret = 0;
    int save_point = 0, cnt = 0;
    vector<int> save;
    for (int n = 0;n < N;n++) {
        if (quest[n].s <= Str || quest[n].i <= Int) {
            if (!visit[n]) {
                visit[n] = true;
                save.push_back(n);
                save_point += quest[n].point;
            }
            cnt++;
        }
    }
    ret = cnt;
    for (int s = Str; s <= min(1000, Str + save_point);s++) {
        int i = min(1000, Int + save_point - (s - Str));
        ret = max(ret, solve(s, i));
    }
    for (int &s : save) visit[s] = false;
    return ret;
}
int main() {
    memset(dp, -1sizeof dp);
    scanf("%d"&N);
    for (int n = 0;n < N;n++)
        scanf("%d%d%d"&quest[n].s, &quest[n].i, &quest[n].point);
    printf("%d\n", solve(11));
    return 0;
}
cs
현재 스탯으로 깰 수 있는 퀘스트의 스탯을 좌표평면상으로 그리면 'ㄴ'자 모양이 되는점을 
이용하여 푼 풀이도 있었다.

10423 전기가 부족해

10423 전기가 부족해 https://www.acmicpc.net/problem/10423

도시와 간선이 주어진다.
도시 중에는 발전소가 있어 전기를 공급해준다.
모든도시에 전기를 공급할 수 있는 최소 비용을 구하는 문제이다.

딱봐도 MST문제다.
도시에 전기가 공급됬는지 배열을 만들어 전기가 공급된 도시와 연결되면 갱신해주었다.
전기가 공급된 도시들의 합이 총 도시 개수이면 중지시킨다.
#include <cstdio>
#include <vector>
#include <algorithm>
using namespace std;
#define mp(a,b) make_pair(a,b)
int N, M, K;
bool elect[1001];
bool connect[1001];
int par[1001], Size[1001];
vector<pair<int,pair<intint>>> adj;
int find(int x) {
    if (par[x] == x) return x;
    return par[x] = find(par[x]);
}
bool merge(int u, int v, int &cnt) {
    int nu = find(u), nv = find(v);
    if (elect[u]) connect[nu] = connect[u] = true;
    if (elect[v]) connect[nv] = connect[v] = true;
    if (nu == nv) return false;
    if (connect[nu] && connect[nv]) return false;
    if (!connect[nu] && !connect[nv]) {
        par[nv] = nu;
        Size[nu] += Size[nv];
        Size[nv] = 1;
    }
    else {
    //적어도 한개가 연결이 안되있을 때
        if (connect[nu]) {
            connect[nv] = true;
            cnt += Size[nv];
            par[nv] = nu;
            Size[nu] += Size[nv];
            Size[nv] = 1;
        }
        else if (connect[nv]) {
            connect[nu] = true;
            cnt += Size[nu];
            par[nu] = nv;
            Size[nv] += Size[nu];
            Size[nu] = 1;
        }
    }
    return true;
}
int main() {
    scanf("%d%d%d"&N, &M, &K);
    for (int n = 1;n <= N;n++) par[n] = n, Size[n] = 1;
    for (int k = 0;k < K;k++) {
        int u;
        scanf("%d"&u);
        elect[u] = true;
    }
    for (int m = 0;m < M;m++) {
        int u, v, w;
        scanf("%d%d%d"&u, &v, &w);
        adj.emplace_back(mp(w, mp(u, v)));
    }
    sort(adj.begin(), adj.end());
    int cnt = 0, ans = 0;
    for (auto &edge : adj) {
        int cost = edge.first;
        int u = edge.second.first, v = edge.second.second;
        if (merge(u, v, cnt)) {
            ans += cost;
            if (cnt == N) break;
        }
    }
    printf("%d\n", ans);
    return 0;
}
cs

사실 더 간단하게 발전소들을 묶어 하나의 집합으로 넣어주고 MST를 구하면 된다.
#include <cstdio>
#include <vector>
#include <algorithm>
using namespace std;
#define mp(a,b) make_pair(a,b)
int N, M, K;
const int save = 0;
bool elect[1001];
bool connect[1001];
int par[1001], Rank[1001];
vector<pair<intpair<intint>>> adj;
int find(int x) {
    if (par[x] == x) return x;
    return par[x] = find(par[x]);
}
bool merge(int u, int v) {
    u = find(u), v = find(v);
    if (u == v) return false;
    if (Rank[u] > Rank[v]) swap(u, v);
    if (Rank[u] == Rank[v]) Rank[v]++;
    Rank[v] += Rank[u];
    par[u] = v;
    return true;
}
int main() {
    scanf("%d%d%d"&N, &M, &K);
    for (int n = 1;n <= N;n++) par[n] = n, Rank[n] = 1;
    for (int k = 0;k < K;k++) {
        int u;
        scanf("%d"&u);
        par[u] = save;    //발전소들을 한 묶음으로 저장
    }
    for (int m = 0;m < M;m++) {
        int u, v, w;
        scanf("%d%d%d"&u, &v, &w);
        adj.emplace_back(mp(w, mp(u, v)));
    }
    sort(adj.begin(), adj.end());
    int cnt = 0, ans = 0;
    for (auto &edge : adj) {
        int cost = edge.first;
        int u = edge.second.first, v = edge.second.second;
        if (merge(u, v)) {
            ans += cost;
            ++cnt;
            if (cnt == N - K + 1break;
        }
    }
    printf("%d\n", ans);
    return 0;
}
cs

10775 공항

10775 공항 https://www.acmicpc.net/problem/10775
3020 개똥벌레 https://www.acmicpc.net/problem/3020
2792 보석상자 https://www.acmicpc.net/problem/2792
3079 입국심사 https://www.acmicpc.net/problem/3079

최근 이분탐색 문제를 많이 풀어서 한꺼번에 올린다.

1. 공항
공항에 $G$개의 게이트가 있다.
비행기 $i$번째 비행기는  [1, $plane_{i}$]게이트 중 하나에 영구적으로 도킹된다.
최대 도킹시킬 수 있는 비행기의 개수를 구하는 문제이다.

set에 도킹되지 않은 게이트들을 저장시켜 greedy하게 $i$번째 비행기를 가장 큰 게이트에 도킹시키면 된다.
#include <cstdio>
#include <algorithm>
#include <set>
using namespace std;
int    N, M;
set<int> v;
int main() {
    scanf("%d%d"&M, &N);
    for (int m = 1;m <= M;m++)
        v.insert(-m);
 
    int ans = 0;
    bool f = false;
    for (int n = 0;n < N;n++) {
        int u;
        scanf("%d"&u);
        auto get = v.lower_bound(-u);
        if (get == v.end()) break;
        v.erase(get);
        ans++;
    }
    printf("%d\n", ans);
    return 0;
}



cs
2. 개똥벌레
석순과 종유석이 번갈아가며 나타나는 동굴이있다.
개똥벌레가 일직선으로 직진하면서 파괴되는 장애물의 개수와 그러한 구간을 구하는 문제이다.

석순은 크기를 찾기위해 lower_bound를 사용하고 
종유석은 높이에서 크기를 뺀값을 upper_bound를 이용해서 찾아냈다.
#include <cstdio>
#include <vector>
#include <algorithm>
using namespace std;
int N, M;
vector<int> up, down;
int main() {
    scanf("%d%d"&N, &M);
    for (int n = 1;n <= N;n++) {
        int u;
        scanf("%d"&u);
        if (n & 1) down.push_back(u);
        else up.push_back(u);
    }
    sort(up.begin(), up.end());
    sort(down.begin(), down.end());
    int ans = 987654321, half_N = N / 2;
    int cnt = 0;
    for (int m = 1;m <= M;m++) {
        int down_iter = lower_bound(down.begin(), down.end(), m) - down.begin();
        int up_iter = upper_bound(up.begin(), up.end(), M - m) - up.begin();
        int ret = half_N - down_iter + half_N - up_iter;
        if (ans > ret) ans = ret, cnt = 1;
        else if (ans == ret) cnt++;
    }
    printf("%d %d\n", ans, cnt);
    return 0;
}
cs
3. 보석상자
M가지 서로다른 색을 가진 모든 보석을 N명에게 나누어주려한다.
보석을 못받는 사람이있어도 되지만 한사람당 같은색상의 보석만 받을 수 있다.
질투심은 가장 많은 보석을 가진 사람이 가지고있는 보석 개수이다.
모든 보석을 나누어줄때 최소 질투심을 구하는 문제이다.

풀이는 코드로 대체한다.
#include <cstdio>
long long N;
int M, arr[300001];
bool isPossible(int mid) {
    long long cnt = 0;
    for (int m = 0;m < M;m++) {
        if (arr[m] % mid == 0)
            cnt += (arr[m] / mid);
        else cnt += (arr[m] / mid) + 1;
        if (cnt > N) return false;
    }
    return true;
}
int main() {
    scanf("%lld%d"&N, &M);
    for (int m = 0;m < M;m++scanf("%d"&arr[m]);
    int l = 1, r = (int)1e9;
    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
4. 입국심사
N개의 심사대는 각각 심사시간을 가진다.
M명의 심사를 마치는데 걸리는 최소시간을 구하는 문제이다.

충분히 큰 범위를 주고 mid값이 M명을 심사할 수 있는지 판별해서 이분탐색으로 돌리면 구할 수 있다.
#include <cstdio>
#include <algorithm>
using namespace std;
int N, M;
int arr[100001];
bool isPossible(long long mid) {
    long long s = 0;
    for (int n = 0;n < N;n++
        s += (mid / arr[n]);
    if (s < 1LL * M) return false;
    return true;
}
int main() {
    scanf("%d%d"&N, &M);
    for (int n = 0;n < N;n++scanf("%d"&arr[n]);
 
    sort(arr, arr + N);
    long long l = 1, r = (long long)1e18;
    while (l <= r) {
        long long mid = (l + r) >> 1;
        if (isPossible(mid)) r = mid - 1;
        else l = mid + 1;
    }
    printf("%lld\n", l);
    return 0;
}
cs