작년에 시행된 서강대 프로그래밍 대회 (Champion 부문) 문제 풀이다.
휴학 상태라 대회를 나가지 못해서 많이 아쉬웠는데, 지금이나마 문제를 한번 풀어보게 되었다.
A. solved.ac (BOJ 18110번)
문제 : https://www.acmicpc.net/problem/18110
n개의 정수를 받아서 그중 상위 15%, 하위 15%를 제외한 나머지 값들의 평균을 구하는 문제이다.
n이 30만 이하이기 때문에 sort함수를 이용하여 nlogn안에 해결 가능하다.
소스코드)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
#include<iostream>
#include<algorithm>
#include<cmath>
#include<functional>
#define MAX 300000
#define FOR(i, N) for(int i = 1; i<=N; i++)
using namespace std;
int arr[MAX+1];
int main(void) {
ios::sync_with_stdio(false);
cin.tie(NULL);
cout.tie(NULL);
int n; cin >> n;
if (n == 0) {
cout << 0;
return 0;
}
FOR(i, n) cin >> arr[i];
sort(arr + 1, arr + n + 1);
int num = round(n*0.15);
int sum = 0;
FOR(i, n - (2 * num)) sum += arr[i+num];
cout << round((double)sum / (n - (2 * num)));
}
|
B. 마인크래프트 (BOJ 18111번)
문제 : https://www.acmicpc.net/problem/18111
이차원 배열의 모든 원소가 같은 값을 갖도록 하는 경우 중, cost가 가장 적은 경우를 구하는 것이다.
높이가 최대 256이고, 배열의 크기가 최대 500x500이기 때문에 입력받은 원소 중 가장 낮은 값부터 시작해서 256까지 완전탐색해도 시간 초과가 발생하지 않는다.
현재 높이보다 더 큰 경우는 제거해주어야 하므로 2를 곱해서 더해주고, 작은 경우에는 바로 더해준다.
소스코드)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
|
#include<iostream>
#include<cmath>
#define INT_MAX 2147483647
using namespace std;
int arr[501][501];
int main(void) {
ios::sync_with_stdio(false); cin.tie(NULL); cout.tie(NULL);
int N, M, B; cin >> N >> M >> B;
int total = 0;
int time = INT_MAX;
int avg = 1000;
for (int i = 0; i < N; i++) {
for (int j = 0; j < M; j++) {
cin >> arr[i][j];
total += arr[i][j];
avg = min(avg, arr[i][j]); //제일 낮은 높이 구하기
}
}
total += B; //놓을 수 있는 최대 블록 수 저장
int ans = 0;
while (avg <= 256 && total >= avg*N*M) {
int temp = 0;
for (int i = 0; i < N; i++) {
for (int j = 0; j < M; j++) {
if (arr[i][j] > avg) temp += 2 * (arr[i][j] - avg);
else temp += (avg - arr[i][j]);
}
}
if (time >= temp) {
time = temp;
ans = avg;
}
avg++;
}
cout << time << ' ' << ans;
}
|
C. 카드 놓기 (BOJ 18115번)
문제 : https://www.acmicpc.net/problem/18115
'덱(Deque)' 개념이다.
입력받은 순서대로 명령을 시행했을 때, N부터 1까지 역순으로 나와야 하는 문제이다.
따라서 가장 마지막 명령부터 거꾸로 가면서, 1이 나오면 덱 앞에 숫자를 추가해주고, 3이 나오면 덱 뒤에 추가해주고,
2가 나오면 덱의 첫 번째 원소를 앞으로 1칸 옮기고 그 자리에 숫자를 추가해준다.
소스코드)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
#include<iostream>
#define MAX 1000000
#define FOR(i, N) for(int i = 1; i<=N; i++)
using namespace std;
int arr[2*MAX + 10];
int card[MAX + 1];
int main(void) {
ios::sync_with_stdio(false); cin.tie(NULL); cout.tie(NULL);
int N; cin >> N;
int a;
int front = MAX + 5;
int end = MAX + 5;
FOR(i, N) cin >> card[i];
for (int i = N; i > 0; i--)
{
if (i == N) {
arr[front--] = 1;
continue;
}
if (card[i] == 1) arr[front--] = N-i+1;
else if (card[i] == 3) arr[++end] = N-i+1;
else {
arr[front] = arr[front+1];
arr[front + 1] = N-i+1;
front--;
}
}
for(int i = front+1; i<=end; i++) cout << arr[i] << ' ';
}
|
D. 로봇 조립 (BOJ 18116번)
문제 : https://www.acmicpc.net/problem/18116
같은 로봇의 부품이라고 알려주는 입력이 부품 두 개가 한 쌍을 이루며 들어온다.
하나의 부품은 둘 이상의 로봇의 부품이 되지 못하고, 어떤 부품이 이루는 로봇의 부품의 개수를 구하는 문제이므로
Disjoint set과 union-find 개념을 이용하면 된다.
각 루트를 -1로 초기화시킨 후, 두 집합이 합쳐질 때 루트 값을 더해준다.
따라서 루트 값의 절댓값이 해당 원소가 속한 집합의 원소의 개수이다.
소스코드)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
|
#include<iostream>
#include<cmath>
#define MAX 1000001
#define FOR(i, N) for(int i = 1; i<=N; i++)
using namespace std;
int v[MAX+1];
int main(void) {
ios::sync_with_stdio(false); cin.tie(NULL); cout.tie(NULL);
int N; cin >> N;
char c;
int a, b;
FOR(i, MAX) v[i] = -1;
while (N--) {
cin >> c;
if (c == 'I') {
cin >> a >> b;
int k1 = a;
int k2 = b;
while (v[k1] > 0) k1 = v[k1];
while (v[k2] > 0) k2 = v[k2];
if (k1 == k2) continue;
if (v[k1] < v[k2]) {
v[k1] += v[k2];
v[k2] = k1;
}
else {
v[k2] += v[k1];
v[k1] = k2;
}
}
else {
cin >> a;
while (v[a] > 0) a = v[a];
cout << abs(v[a]) << '\n';
}
}
}
|
E. 분수 (BOJ 18117번)
문제 : https://www.acmicpc.net/problem/18117
a와 b를 입력받아서, a/b를 소수로 나타내어 소수점 아래 i번째 자리부터 n개의 숫자를 구하는 문제이다.
i의 범위가 10^18이므로 i번째까지 도달하기 위해서는 분할 정복을 통해 빠른 거듭제곱을 하는 방법을 이용해주어야 한다. 이 문제를 풀 때, overflow 때문에 매우 고생했다.... 거듭제곱을 할 때 unsigned long long의 범위도 초과해버려 막혔었는데, 찾아보니 거듭제곱뿐만 아니라 단순 곱셈(mul 함수)도 분할 정복 기법을 이용할 수 있었다.
우선 i번째 자릿수가 나올 때까지 10을 거듭제곱하면서 곱해준다. 그다음은 10씩 곱하면서 1의 자리를 출력해준다.
소스코드)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
|
#include<iostream>
#include<cmath>
#define ll long long int
#define FOR(i, N) for(int i = 1; i<=N; i++)
using namespace std;
ll mul(ll x, ll y, ll mod) {
ll res = 0;
while (y) {
if (y % 2) res = (res + x) % mod;
x = (x * 2) % mod;
y /= 2;
}
return res;
}
int main(void) {
ios::sync_with_stdio(false);
cin.tie(NULL);
cout.tie(NULL);
int T; cin >> T;
while (T--) {
ll a, b, i, n; cin >> a >> b >> i >> n;
i -= 1;
ll x = 10;
while (i) {
if (i % 2) a = mul(a, x, b);
i /= 2;
x = mul(x, x, b);
}
FOR(j, n) {
cout << floor(((double)a / b) * 10);
a = mul(a, 10, b);
}
cout << '\n';
}
}
|
F. 대농부 김상혁 (BOJ 18120번)
최대 이익이 되도록 농토의 반지름을 결정해주는 문제이다.
이차방정식을 구하여 해결하는 문제로, 주의해야 하는 점은 반경 r 이내에 속하지 않은 농작물은 이익에 영향을 주지 못하는 점이다. (예시로는 그런 경우가 없어 처음에 미처 생각하지 못했었다.)
각 농작물의 좌표를 $ (x_i, y_i) $ 라고 하자.
우선 농토를 관리하는데 드는 비용은 $ Ar^2 $이다.
그리고 농작물로부터 농토 경계까지의 최단 거리를 $ c_i $ 라고 하면, $ c_i $는 농토의 중심 $ (0, 0) $에서 농작물까지의 거리를 $ r $에서 빼준 것이 된다. 즉, $ c_i = r-\sqrt{x_i^2 + y_i^2} $ 가 된다.
그러므로 반지름이 $ r $일 때, 총 농작물의 이익은 $ \sum_{\sqrt{x_i^2 + y_i^2} \leq r}^{}c_iw_i $ $ - $ $ Ar^2 $
즉, $ \sum_{\sqrt{x_i^2 + y_i^2} \leq r}^{}(r - \sqrt{x_i^2 + y_i^2})w_i $ $ - $ $ A * r^2 $ 이 된다.
(r안에 속하는 농작물들만 포함)
이를 r에 대한 이차방정식으로 정리하면, $-Ar^2 + r\sum_{\sqrt{x_i^2 + y_i^2} \leq r}^{}w_i$ $ - $ $ \sum_{\sqrt{x_i^2 + y_i^2} \leq r} w_i\sqrt{x_i^2 + y_i^2}$ 가 된다.
r 범위 안에 포함되는 농작물들의 가치를 모두 더한 것을 $ K $라고 하고, 원점으로 부터의 거리와 가치의 곱을 모두 더한 것을 L이라고 한다면 다음과 같이 표현이 된다.
$ -Ar^2 + Kr - L $ = $ -A(r^2 - \frac{Kr}{A}+ \frac{(Kr)^2}{4A^2}) +\frac{(Kr)^2}{4A^2}- L $ = $ -A(r-\frac{K}{2A})^2 +\frac{(Kr)^2}{4A^2}- L $
농작물들을 원점으로부터의 거리가 오름차순이 되도록 정렬을 해두고, r을 0부터 키워나가는 방식으로 구하면,
$ K $는 농작물이 하나씩 포함될 때마다 커질 것이고, L값 또한 마찬가지로 농작물이 하나씩 포함될때마다 커질 것이다.
그러므로 t번째 농작물까지 포함했을 때 최대 이익은
1) r = 원점 ~ t번째 농작물까지의 거리
2) r = 원점 ~ t+1번째 농작물까지의 거리 - $ \varepsilon $
3) r = t번째와 t+1번째 농작물의 사이
총 세 가지 경우중 하나에서 발생하게 된다.
그리고 이차함수는 농작물이 하나씩 포함될 때마다 식이 달라질 것이다. 그러므로 각 농작물마다 구간을 나누어서 구간별로 이차함수를 생각해주면 된다.
만약 이차함수의 꼭짓점이 t번째와 t+1번째 농작물 사이에 위치한다면 3)의 경우에 최대 이익이 발생하므로 $ r = \frac{K}{2A} $를 대입해준다.
2)의 경우에는 아주 작은 임의의 양수 $ \varepsilon $은 무시하고 거리만 t+1번째 농작물까지, $K$와 $L$에는 t번째 농작물까지 포함시켜서 계산해주면 된다.
소스코드)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
|
#include<iostream>
#include<algorithm>
#include<stack>
#include<cmath>
#include<functional>
#include<string>
#include<vector>
#include<cstring>
#include<utility>
#define FOR(i, N) for(int i = 1; i<=N; i++)
using namespace std;
bool comp(pair<double, int>p1, pair<double, int>p2) {
return p1 < p2;
}
int main(void) {
ios::sync_with_stdio(false); cin.tie(NULL); cout.tie(NULL);
int N, A; cin >> N >> A;
double temp = 0;
double w_sum = 0;
vector<pair<double, int>>p(N);
FOR(i, N) {
int x, y, w; cin >> x >> y >> w;
p[i - 1].first = sqrt(x*x + y*y);
p[i - 1].second = w;
temp += w*sqrt(x*x + y*y);
w_sum += w;
}
sort(p.begin(), p.end(), comp);
double ans = pow(w_sum, 2) / (4 * A) - temp;
w_sum = 0;
temp = 0;
double r = 0;
FOR(i, N) {
double r1 = w_sum / (2 * A);
if (r <= r1 && r1<= p[i - 1].first) { //middle
ans = max(-A*r1*r1 + r1*w_sum - temp, ans);
}
r = p[i - 1].first;
ans = max((-A)*r*r + r*w_sum - temp , ans); //left
w_sum += p[i - 1].second;
temp += p[i - 1].first*p[i - 1].second;
ans = max((-A)*r*r + r*w_sum - temp, ans); //right
}
cout << fixed;
cout.precision(6);
if (ans < 0) cout << 0;
else cout << ans;
}
|
G와 H는 어려워서 아직 시도를 못했다...
해설집을 참고하니 트라이랑 AHU알고리즘이 필요하다는데 해당 알고리즘을 제대로 공부한 후, 풀어볼 예정이다.
'프로그래밍 대회 풀이 > Sogang Programming Contest' 카테고리의 다른 글
2020 Sogang Programming Contest Open 후기 및 풀이 (미완) (0) | 2020.11.30 |
---|