문제

 

풀이

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

이 문제와 비슷한 방식으로 접근할 수 있습니다.

 

카드 주머니가 N개 있고, 주머니 안에는 각각 1 ~ Ai가 적힌 카드가 있습니다.

여기서 연속된 K개의 카드 주머니를 고르고, 그 카드 주머니에서 카드 하나씩 꺼내서

1, 2, 3, ...., K까지 하나씩 등장하도록 만들어야 하고, 이 때 K의 최댓값을 구하는 문제입니다.

 

이 문제는 고민만 하다가 끝났었는데 Two Pointer + Segment Tree Lazy로 풀리는 문제였습니다.

트리에는 해당 숫자를 몇 번 사용할 수 있는지 카운팅을 해줍니다.

만약 N = 5라면 1은 1번, 2는 2번, 3은 3번, 4는 4번, 5는 5번 사용할 수 있게 됩니다.

주머니의 크기가 5 5 5 5 5라면 크기가 5인 주머니를 5번 사용할 수 있으며, 5 5 5 5 5 5로는 6을 만들 수 없습니다.

 

그 다음에는 l = 0, r = 0으로 시작하여 해당 범위에 속한 K개를 모두 사용하여 1 ~ K까지 하나씩 만들 수 있는지 확인합니다.

r번째 주머니를 사용한게 되기 때문에 list[r] ~ maxValue의 범위에 -1씩 카운팅을 해줍니다.

이 과정에서 트리의 값이 음수가 되었다면 1 ~ K를 고르지 못한게 됩니다.

그래서 다시 음수에서 벗어날때까지 l을 오른쪽으로 이동시켜줍니다.

트리의 값이 음수인 것을 빠르게 캐치하려면 트리에 최솟값을 저장해야 합니다.

 

5
1 1 2 5 3

예시로 설명드리면

맨 처음 트리의 상태는 1 2 3 4 5이고, 최솟값은 1입니다. (l = 0, r = 0)

 

여기서 범위를 오른쪽으로 1칸 늘립니다. (l = 0, r = 1)

값이 1이기 때문에 1 ~ 5의 카운팅을 1씩 감소시킵니다.

그러면 트리의 상태는 0 1 2 3 4가 되고, 최솟값은 0입니다.

 

다음 범위를 오른쪽으로 1칸 늘립니다. (l = 0, r = 2)

값이 1이기 때문에 1 ~ 5의 카운팅을 1씩 감소시킵니다.

그러면 트리의 상태는 -1 0 1 2 3이 되고, 최솟값은 -1입니다.

이때 음수가 되었기 때문에 l을 오른쪽으로 1칸씩 이동시킵니다. (l = 1, r = 2)

직전 l번째 수는 1이었기 때문에 1 ~ 5의 카운팅을 1씩 늘립니다.

그러면 트리의 상태는 0 1 2 3 4가 되고, 최솟값은 0이 되어 1 ~ K를 다시 만들 수 있게 됩니다.

 

다음 범위를 오른쪽으로 1칸 이동합니다. (l = 1, r = 3)

값이 2이기 때문에 2 ~ 5의 카운팅을 1씩 감소합니다.

트리의 상태를 0 0 1 2 3이 되고, 최솟값은 0이기 때문에 범위를 갱신할 수 있습니다.

 

이 과정을 끝까지 반복하여 l ~ r의 범위를 최대로 만들 수 있습니다.

 

코드

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
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
#include <iostream>
#include <vector>
#include <algorithm>
#define MAX 50001
using namespace std;
 
int list[MAX], tree[MAX << 2], lazy[MAX << 2];
int N, maxValue;
 
void lazyUpdate(int node, int l, int r) {
    if (!lazy[node]) return;
    tree[node] += lazy[node];
    if (l != r) {
        lazy[node << 1+= lazy[node];
        lazy[(node << 1+ 1+= lazy[node];
    }
    lazy[node] = 0;
}
 
int update(int node, int l, int r, int s, int e, int diff) {
    lazyUpdate(node, l, r);
    if (l > e || s > r) return tree[node];
    if (s <= l && r <= e) {
        tree[node] += diff;
        if (l != r) {
            lazy[node << 1+= diff;
            lazy[(node << 1+ 1+= diff;
        }
        return tree[node];
    }
 
    int m = (l + r) >> 1;
    return tree[node] = min(update(node << 1, l, m, s, e, diff), update((node << 1+ 1, m + 1, r, s, e, diff));
}
 
int query(int node, int l, int r, int s, int e) {
    lazyUpdate(node, l, r);
    if (l > e || s > r) return 1e9;
    if (s <= l && r <= e) return tree[node];
 
    int m = (l + r) >> 1;
    return min(query(node << 1, l, m, s, e), query((node << 1+ 1, m + 1, r, s, e));
}
 
void init() {
    for (int i = 1; i <= N; i++) {
        update(11, maxValue, i, maxValue, 1);
    }
}
 
void func() {
    init();
    int ret = 1;
    int l = 0;
    int r = 0;
    while (r < N) {
        update(11, maxValue, list[r], maxValue, -1);
 
        while (query(11, maxValue, list[r], maxValue) < 0) {
            update(11, maxValue, list[l], maxValue, 1);
            l++;
        }
        r++;
 
        ret = max(ret, r - l);
    }
 
    cout << ret << '\n';
}
 
void input() {
    cin >> N;
    for (int i = 0; i < N; i++) {
        cin >> list[i];
        maxValue = max(maxValue, list[i]);
    }
}
 
int main() {
    cin.tie(NULL); cout.tie(NULL);
    ios::sync_with_stdio(false);
 
    input();
    func();
 
    return 0;
}
cs

문제

 

풀이

(1, 1)에서 출발하여 E L I C E를 한글자씩 순서대로 모두 모을 수 있는 최소 거리를 구하는 문제입니다.

여기서 E는 2개의 위치가 주어지지만 동일한 글자로 취급하기 때문에 어떤 곳을 먼저가도 상관 없습니다.

어떻게하면 더 효율적으로 구할 수 있는지 생각해봤지만 다익스트라로 간단하게 구할 수 있는 문제였습니다.

 

저는 우선 ELICE에 해당하는 글자들의 좌표를 한 배열에 넣고, 출발 지점인 (1, 1)은 맨 앞에 넣습니다.

그리고 배열에 저장된 순서대로 i번째 지점 ~ i + 1번째 지점의 거리를 구해주시면 됩니다.

 

start E L I C E를 인덱스로 0 1 2 3 4 5로 표현한다면

0 1 2 3 4 5에서 각각 인접한 좌표까지의 거리를 구하고,

E의 위치인 1과 5를 바꿔서 0 5 2 3 4 1으로 만들고 각각 인접한 좌표까지의 거리를 다시 구합니다.

위 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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
#include <iostream>
#include <map>
#include <queue>
#include <vector>
#define MAX 1001
using namespace std;
typedef pair<intint> pii;
 
int d[MAX][MAX];
int list[MAX][MAX];
int alpha[6][2];
int direct[4][2= { {0,1},{1,0},{0,-1},{-1,0} };
int N;
 
void init() {
    for (int i = 1; i <= N; i++) {
        fill(d[i] + 1, d[i] + 1 + N, 1e9);
    }
}
 
int dijkstra(int sx, int sy, int ex, int ey) {
    init();
    priority_queue<pair<int, pii>vector<pair<int, pii> >, greater<pair<int, pii> > > q;
    q.push({ 0, {sx,sy} });
    d[sx][sy] = 0;
 
    while (!q.empty()) {
        int x = q.top().second.first;
        int y = q.top().second.second;
        int dis = q.top().first;
        q.pop();
 
        if (d[x][y] < dis) continue;
 
        for (int i = 0; i < 4; i++) {
            int nx = x + direct[i][0];
            int ny = y + direct[i][1];
            int nextDis = dis + list[x][y] + list[nx][ny];
 
            if (nx < 1 || ny < 1 || nx > N || ny > N) continue;
            if (d[nx][ny] <= nextDis) continue;
 
            d[nx][ny] = nextDis;
            q.push({ nextDis, {nx,ny} });
        }
    }
 
    return d[ex][ey];
}
 
void func() {
    int ret = 1e9;
    for (int t = 0; t < 2; t++) {
        int sum = 0;
        for (int i = 0; i < 5; i++) {
            sum += dijkstra(alpha[i][0], alpha[i][1], alpha[i + 1][0], alpha[i + 1][1]);
        }
        ret = min(ret, sum);
        swap(alpha[1], alpha[5]);
    }
 
    cout << ret << '\n';
}
 
void input() {
    cin >> N;
    for (int i = 1; i <= N; i++) {
        for (int j = 1; j <= N; j++) {
            cin >> list[i][j];
        }
    }
 
    alpha[0][0= 1;
    alpha[0][1= 1;
    for (int i = 1; i <= 5; i++) {
        cin >> alpha[i][0>> alpha[i][1];
    }
}
 
int main() {
    cin.tie(NULL); cout.tie(NULL);
    ios::sync_with_stdio(false);
 
    input();
    func();
 
    return 0;
}
cs

문제

 

풀이

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

이 문제와 같은 풀이입니다.

 

엘리스는 강림제에 참여한 신도들이 T명 이상 모여야 지상에 강림한다고 합니다.

만약 신도들이 T명 미만으로 모였을 때 친구를 최대 K명까지 초대하여 T명을 맞출 수도 있습니다.

친구들은 친구들을 제외한 신도들이 T명 이상이 되었다면 모두 탈출한다고 하여 적절한 시기에만 친구를 불러야 합니다.

 

저는 dp로 문제에 접근했고, 우선 누적합을 통해 참여하는 신도들의 수를 시간별로 구합니다.

범위로 누적합을 구해야하기 때문에 imos 기법을 활용했습니다.

imos 기법으로 범위의 양 끝지점에만 카운팅을 먼저 하고, 마지막에 한번에 누적합을 구할 수 있습니다.

 

dp[N][K][pre]: N 시간에 친구 K명을 부를 수 있고, 이미 참여중인 친구가 pre명일 때 엘리스가 지상으로 강림하는 최대 시간

문제에서 나올 수 있는 경우는 4가지입니다.

1. 신도들만 T명 이상 모였을 경우

2. 신도 + 이미 참여중인 친구들이 T명 이상 모였을 경우

3. 친구들을 더 추가하여 T명을 채울 수 있는 경우

4. 친구들을 추가해도 T명을 채우지 못하는 경우

 

1번의 경우는 엘리스가 강림할 수 있으므로 +1을 하고 다음 시간을 확인합니다.

이미 참여중인 친구들이 있다면 모두 탈출하기 때문에 pre는 0으로 맞춥니다.

 

2번의 경우도 엘리스가 강림할 수 있으므로 +1을 하고 다음 시간을 확인합니다.

친구들을 제외한 시도들이 T보다 적으므로 pre도 그대로 유지합니다.

 

3번의 경우도 엘리스가 강림할 수 있으므로 +1을 하고 다음 시간을 확인합니다.

친구를 tmp명 더 추가합니다. pre는 pre + tmp가 되겠고, k에서도 tmp만큼 빼줍니다.

 

4번의 경우는 엘리스가 강림할 수 없으므로 바로 다음 시간을 확인합니다.

이때 친구들을 더 추가하지 않으며, 이미 참여중인 친구들은 계속 참여하기 때문에 pre를 그대로 유지합니다.

 

코드

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
59
60
#include <iostream>
#include <cstring>
#include <algorithm>
#define MAX 310
using namespace std;
 
int sum[MAX];
int dp[MAX][MAX][MAX];
int N, M, K, T;
 
int solve(int idx, int k, int pre) {
    if (idx > N) return 0;
    int& ret = dp[idx][k][pre];
    if (ret != -1return ret;
 
    if (sum[idx] + pre >= T) {
        int next = pre;
        if (sum[idx] >= T) next = 0;
        return ret = solve(idx + 1, k, next) + 1;
    }
 
    ret = solve(idx + 1, k, pre);
    int tmp = T - sum[idx] - pre;
    if (k && k >= tmp) {
        ret = max(ret, solve(idx + 1, k - tmp, pre + tmp) + 1);
    }
    return ret;
}
 
void init() {
    memset(dp, -1sizeof(dp));
    for (int i = 1; i < MAX; i++) {
        sum[i] += sum[i - 1];
    }
}
 
void func() {
    init();
    cout << solve(1, K, 0<< '\n';
}
 
void input() {
    int l, r;
    cin >> N >> M >> K >> T;
    while (M--) {
        cin >> l >> r;
        sum[l]++;
        sum[r]--;
    }
}
 
int main() {
    cin.tie(NULL); cout.tie(NULL);
    ios::sync_with_stdio(false);
 
    input();
    func();
 
    return 0;
}
cs

문제

 

풀이

처음 이 문제를 봤을때 손도 못댔었는데 정답 코드를 보니 조금 허무했습니다.

이런게 애드혹이겠죠.. ㅠ

 

우선 N의 범위가 10^7이기 때문에 범위 내에서는 8종류의 수까지 표현할 수 있습니다.

따라서 K가 9, 10인 경우에는 9, 10종류의 수를 사용한 가장 작은수를 출력하면 그게 답입니다.

K = 9일 때는102345678이 되겠고, K = 10일 때는 1023456789가 되겠습니다.

 

그러면 나머지의 경우는 많아봐야 1천만 정도라서 N을 1씩 증가시키면서 직접 비교해도 시간 안에 문제를 해결할 수 있습니다.

저는 N + 1을 문자열로 만들어서 각 자리를 카운팅한 다음 카운팅한 갯수가 정확이 K개인지 확인하는 방식으로 구현했습니다.

N + 1을 계속 하기때문에 무조건 N보다는 큰 숫자일 수밖에 없고, 그 수가 정확이 K개의 숫자만 사용했다면 그게 정답이 됩니다.

 

코드

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
#include <iostream>
#include <string>
#include <cstring>
using namespace std;
 
string str;
int K;
bool chk[10];
 
void func() {
    if (K == 10) {
        cout << "1023456789\n";
        return;
    }
    if (K == 9) {
        cout << "102345678\n";
        return;
    }
 
    while (1) {
        str = to_string(stoi(str) + 1);
        int cnt = 0;
        for (auto x : str) {
            if (chk[x - '0']) continue;
            cnt++;
            chk[x - '0'= true;
        }
 
        if (cnt == K) {
            cout << str << '\n';
            return;
        }
 
        memset(chk, falsesizeof(chk));
    }
}
 
void input() {
    cin >> str >> K;
}
 
int main() {
    cin.tie(NULL); cout.tie(NULL);
    ios::sync_with_stdio(false);
 
    input();
    func();
 
    return 0;
}
cs

문제

 

풀이

입력에는 빨간 간선인지, 파란 간선인지 주어집니다.

그러면 정점 2개를 골라서 각 정점이 포함된 연결 요소의 모든 정점들을 서로 연결시켜줍니다.

이때 사용되는 간선들 중 빨간 간선의 갯수가 최소가 되도록 정점을 적절하게 골라야 합니다.

 

처음에는 그냥 연결된 간선이 적은 정점 2개를 골라주면 되는것 아닌가? 라는 생각이 들었었고,

우선순위 큐를 이용하여 작은 값 2개의 합을 더해주는 방식으로 구현했더니 60점이 나왔었습니다.

이 방법이 맞다면 반례가 없을텐데 정답이 아닌걸로 봐서 제가 접근한 방식이 틀린거였습니다.

 

문제의 솔루션은 bfs + map을 사용하는 것이었습니다.

배열에는 각 연결요소에 포함된 정점의 갯수를 저장합니다.

N이 5라고 가정하면 처음에는 1 1 1 1 1이 배열에 저장됩니다.

그리고 이 배열의 상태를 방문처리 합니다.

 

그 다음 bfs를 통해 각 연결요소들을 2개씩 뽑아서 연결합니다.

이때 배열을 정렬해줘야 방문처리를 할 수 있습니다.

각 연결요소의 크기가 1 1 2 2인 것과 2 1 2 1인 것 둘다 똑같은 결과가 나옵니다.

그리고 그 간선이 빨간 간선일 때만 갯수를 세어줍니다.

 

해당 연결요소들의 상태가 map에 없을때만 queue에 넣어주고, map에 있을때는 최솟값을 갱신해주도록 합니다.

모든 정점이 연결되면 연결요소의 상태가 {N}이 되며, 이를 map에서 꺼내면 답을 구할 수 있습니다.

 

코드

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
59
60
61
62
63
64
65
66
67
68
69
70
#include <iostream>
#include <algorithm>
#include <vector>
#include <queue>
#include <map>
#define MAX 31
using namespace std;
 
int list[MAX];
int N;
 
int bfs() {
    vector<int> v = vector<int>(N, 1);
    map<vector<int>int> m;
    m[v] = 0;
    queue<vector<int> > q;
    q.push(v);
    for (int t = 0; t < N - 1; t++) {
        int qsize = q.size();
        while (qsize--) {
            auto x = q.front();
            q.pop();
 
            int size = x.size();
            for (int i = 0; i < size - 1; i++) {
                for (int j = i + 1; j < size; j++) {
                    vector<int> tmp;
                    for (int k = 0; k < size; k++) {
                        if (i == k || j == k) continue;
                        tmp.push_back(x[k]);
                    }
                    tmp.push_back(x[i] + x[j]);
                    sort(tmp.begin(), tmp.end());
                    if (m.find(tmp) == m.end()) {
                        q.push(tmp);
                        if (!list[t]) m[tmp] = m[x] + x[i] * x[j];
                        else m[tmp] = m[x];
                    }
                    else {
                        if (!list[t]) m[tmp] = min(m[tmp], m[x] + x[i] * x[j]);
                        else m[tmp] = min(m[tmp], m[x]);
                    }
                }
            }
        }
    }
 
    return m[{N}];
}
 
void func() {
    cout << bfs() << '\n';
}
 
void input() {
    cin >> N;
    for (int i = 0; i < N - 1; i++) {
        cin >> list[i];
    }
}
 
int main() {
    cin.tie(NULL); cout.tie(NULL);
    ios::sync_with_stdio(false);
 
    input();
    func();
 
    return 0;
}
cs

문제

 

풀이

이 문제는 방법이 다양한것 같았습니다. dfs로 푸신분들도 계셨지만 저는 최근에 그리디를 좀 많이 풀었어서 그런지 그리디로 먼저 접근했습니다.

 

우선 수열이 a1, a2, a3, ... an 까지 존재한다고 했을 때 이 수열의 부분수열의 합을 2^n개 만들 수 있습니다.

하지만 문제에서 주어지는 입력은 그 부분수열의 합인 2^n개의 수가 주어집니다.

따라서 어떤 수열의 조합으로 입력되는 부분 수열의 합을 만들 수 있는지 원래 수열의 원소들을 구하는 문제입니다.

 

처음 생각해본건 두가지입니다.

1. 입력으로 주어지는 수열 중 0은 절대 답이 될 수 없음

2. 0 다음으로 제일 작은 수는 무조건 원래 수열의 원소에 해당함

1번은 원래 수열의 원소 ai의 범위는 1 ~ 100000입니다. 0을 만들려면 어떤 원소도 더하지 않는 경우밖에 없습니다.

2번은 0 다음으로 제일 작은 수는 어떤 수를 더하더라도 만들 수 없습니다. 그냥 자신만이 그 수를 만들 수 있습니다.

0 다음으로 제일 작은 수가 여러개라면 그 같은 수들을 모두 더해주도록 합니다.

 

여기서 시작해서 어떻게 그리디하게 접근할 수 있을까 고민하다가 map과 set을 사용하게 되었습니다.

map과 set에는 원래 수열로 인해 제거되는 부분 수열의 값을 저장합니다.

제가 수를 제거한 방식으로는

set에 지금까지 모은 원래 수열의 부분 수열의 합을 모두 다 저장합니다.

map에는 set에서 구한 합들을 카운팅합니다.

 

예를들어 지금까지 구한 원래 수열이 1, 2, 3라고 했을 때 얘내들의 부분 수열의 합은 0 1 2 3 3 4 5 6 입니다.

그리고 다음 추가되는 원소가 4라고 했을 때 원래 수열은 1, 2, 3, 4가 되고,

이전에서 구했던 부분 수열의 합에서 모두 +4를 해주면 4 5 6 7 7 8 9 10이 되는데,

이걸 부분 수열의 합으로 모두 추가하면 0 1 2 3 3 4 4 5 5 6 6 7 7 8 9 10이 되고, 이는 수열 1, 2, 3, 4의 부분 수열의 합이 됩니다.

 

위에서 구한 값들을 set과 map에 넣어주고,

다음 들어오는 값을 key로 map에 검색하여 1 이상으로 카운팅되어 있다면 선택하지 않고 넘어가는 방식입니다. 이때 map[key]를 1씩 감소시킵니다.

여기서 map을 사용한 이유는 원래 수열의 원소를 원래 수열의 합으로 만들 수도 있기 때문입니다.

2 4 6이 원래 수열이라면 2 + 4로도 6을 만들 수 있기 때문에 6이 걸러지는 문제가 있었습니다. 덕분에 코드가 더러워졌습니다...

map에서 카운팅한 만큼의 수를 걸러내고, 제거되지 않은 수들 중 제일 작은 수를 순서대로 넣어주면 원래 수열을 구할 수 있습니다.

 

코드

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
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
#include <iostream>
#include <algorithm>
#include <vector>
#include <map>
#include <set>
#define MAX 1500001
using namespace std;
 
set<int> s;
map<intint> m;
vector<int> v;
int list[MAX];
int N;
 
void eraseNum(int x) {
    set<int>::iterator iter = s.begin();
    vector<int> tmp;
    for (; iter != s.end(); iter++) {
        tmp.push_back(*iter + x);
    }
 
    for (auto y : tmp) {
        m[y]++;
        s.insert(y);
    }
}
 
void init() {
    sort(list, list + (1 << N));
 
    v.push_back(list[1]);
    m[0]++;
    m[list[1]]++;
    s.insert(0);
    s.insert(list[1]);
    for (int i = 2; i < (1 << N); i++) {
        if (list[1!= list[i]) break;
 
        v.push_back(list[i]);
        eraseNum(list[i]);
    }
}
 
void func() {
    init();
    for (int i = 2; i < (1 << N); i++) {
        if (m.find(list[i]) != m.end()) {
            m[list[i]]--;
            if (!m[list[i]]) m.erase(list[i]);
            continue;
        }
        if (v.size() == N) break;
 
        v.push_back(list[i]);
        eraseNum(list[i]);
        for (int j = i + 1; j < (1 << N); j++) {
            if (list[i] != list[j]) break;
            if (v.size() == N) break;
            v.push_back(list[j]);
            eraseNum(list[j]);
        }
    }
 
    for (auto x : v) {
        cout << x << ' ';
    }
    cout << '\n';
}
 
void input() {
    cin >> N;
    for (int i = 0; i < (1 << N); i++) {
        cin >> list[i];
    }
}
 
int main() {
    cin.tie(NULL); cout.tie(NULL);
    ios::sync_with_stdio(false);
 
    input();
    func();
 
    return 0;
}
cs

문제

 

풀이

처음 이 문제를 봤을 때 지문에 대한 이해를 전혀 못했습니다.

말을 1인 1개씩 2개를 놓고 게임을 하는건지, 말 1개를 놓고 둘이서 게임을 하는건지도 이해가 안돼서 로직을 그리는 내내 예시도 맞지 않아서 답답했었습니다.

 

문제를 이해하는데 4시간은 걸린것 같습니다.

이 문제는 말을 1개만 놓고 둘이서 게임하는게 맞고, 1번 정점이 루트인 트리라고 명시가 되어있기 때문에 1번 정점부터 방향그래프처럼 순회하도록 합니다.

 

한 번씩 번갈아가면서 점수를 가져가기 때문에 선공의 입장에서는 +, 후공의 입장에서는 -으로 계산할 수 있습니다.

서로 최대한 이기려고 할 것이고, 이기는 경우가 하나라도 존재한다면 이기는 것이기 때문에 가능한 높은 점수를 얻는 것이 중요합니다.

그럴려면 자식 노드의 점수들 중 최솟값을 본인 점수에서 빼는 것으로 계산할 수 있습니다.

선공의 입장에서 계산했기 때문에 해당 노드의 점수가 0 이상이면 선공의 승리, 음수면 후공의 승리가 될 것입니다.

 

2번째 예시로 로직을 설명드리면 루트는 1번, 리프는 4, 5, 6번 노드입니다.

4, 5, 6번 노드에 도착하면 해당 번호만큼 점수를 추가합니다. 그러면 4, 5, 6번 노드의 점수는 4, 5, 6입니다.

 

2번 노드에서는 자신의 번호인 2에서 유일한 자식인 4번 노드의 점수 4를 뺀 값을 저장합니다. 그러면 2번 노드의 점수는 -2가 됩니다.

 

3번 노드는 자식이 2개 있습니다.

5번, 6번 노드의 점수 중 최솟값을 3에서 빼줍니다. 그러면 3번 노드의 점수는 3 - 5 = -2 입니다.

 

마지막 1번 노드입니다.

1번 노드의 자식인 2, 3번 노드 모두 점수가 -2이므로 1 + 2 = 3 점을 얻습니다.

 

그러면 최종 점수 배열은 [3, -2, -2, 4, 5, 6] 이 되고, 0 이상의 점수에는 1, 음수 점수에는 0을 출력하도록 합니다.

 

 

코드

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
#include <iostream>
#include <vector>
#include <cstring>
#define MAX 100001
using namespace std;
 
vector<int> graph[MAX];
bool visit[MAX];
int score[MAX];
int N;
 
int dfs(int v) {
    visit[v] = true;
 
    int tmp = 1e9;
    for (auto next : graph[v]) {
        if (visit[next]) continue;
        tmp = min(tmp, dfs(next));
    }
    if (tmp == 1e9) tmp = 0;
 
    return score[v] = -tmp + v;
}
 
void func() {
    dfs(1);
 
    for (int i = 1; i <= N; i++) {
        if (score[i] >= 0cout << "1\n";
        else cout << "0\n";
    }
}
 
void input() {
    int u, v;
    cin >> N;
    for (int i = 1; i < N; i++) {
        cin >> u >> v;
        graph[u].push_back(v);
        graph[v].push_back(u);
    }
}
 
int main() {
    cin.tie(NULL); cout.tie(NULL);
    ios::sync_with_stdio(false);
 
    input();
    func();
 
    return 0;
}
cs

문제

 

풀이

괄호 안에 존재하는 수를 괄호 바로 앞에 있는 수만큼 반복한 문자열로 만들 수 있고, 모든 괄호를 제거했을 때 문자열의 길이를 구하는 문제입니다.

처음에는 재귀를 통해서 문자열을 직접 구해서 해결했었는데

어떤분께서 게시글에 int 범위가 벗어난다는 의견을 제시해주셔서 다시 풀게 되었습니다. 아마 저는 틀렸겠지요... ㅠ

 

생각해보니 문자열을 직접 구할 필요가 없더라고요! 길이만 구하면 되니까요!

그래서 이번에는 스택으로 접근했습니다.

문제의 핵심은 괄호 안의 숫자를 k번 반복한다는 것입니다.

그러면 괄호 안의 숫자의 길이 * k가 스택에 들어가면 된다는거겠죠.

 

그러면 스택에는 숫자와 괄호를 구분할 필요가 생깁니다.

우선 숫자가 나온다면 다음 문자가 "(" 인지 확인해야 합니다.

다음 문자가 "("가 아니라면 스택에 1을 넣으시면 됩니다. (길이가 1인 문자열)

다음 문자가 "("이라면 반복에 활용되는 k에 해당되기 때문에 해당 숫자를 스택에 넣습니다.

 

다음은 괄호가 나왔을 경우입니다.

현재 위치가 "("이면 괄호 구분을 위해 스택에 -1을 넣습니다.

스택에는 양의 정수만 들어가기 때문에 이런 방식으로 숫자와 괄호를 구분할 수 있습니다.

 

현재 위치가 ")"이면 스택에 쌓여있는 문자열의 길이들을 더해주도록 합니다.

괄호 짝인 -1이 나올 때까지만 더해주시면 됩니다.

그리고 -1 바로 다음에 있는 수를 꺼내서 곱한 값을 스택에 다시 넣어줍니다.

그러면 3(8) = 888의 과정이 마무리된 것입니다.

-1이 열리는 괄호가 되겠고, 바로 다음에 있는 수가 3에 해당됩니다. 8은 당연히 -1이 나오기 전까지 더한 값들이 됩니다.

 

위 과정들이 모두 끝난다면 스택에는 문자열의 길이가 여러개 들어있을 것입니다.

그 숫자들을 모두 더해주시면 답을 구할 수 있습니다.

문제에는 수의 범위가 명시되어있지 않기 때문에 lnog long 자료형으로 출력해주도록 합니다.

 

코드

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
#include <iostream>
#include <string>
#include <stack>
using namespace std;
typedef long long ll;
 
string str;
int N;
 
void func() {
    stack<ll> s;
    for (int i = 0; i < N; i++) {
        if (str[i] == '(') {
            s.push(-1);
        }
        else if (str[i] == ')') {
            ll sum = 0;
            while (1) {
                ll x = s.top();
                s.pop();
                if (x == -1break;
                sum += x;
            }
            ll tmp = s.top();
            s.pop();
            s.push(tmp * sum);
        }
        else if (i < N - 1 && str[i + 1== '(') {
            s.push((ll)(str[i] - '0'));
        }
        else s.push(1LL);
    }
 
    ll ret = 0;
    while (!s.empty()) {
        ret += s.top();
        s.pop();
    }
    cout << ret << '\n';
}
 
void input() {
    cin >> str;
    N = str.size();
}
 
int main() {
    cin.tie(NULL); cout.tie(NULL);
    ios::sync_with_stdio(false);
 
    input();
    func();
 
    return 0;
}
cs

문제

 

풀이

주어진 배열에서 l ~ r의 범위를 골라서 오름차순으로 정렬하고 idx번째에 위치한 수를 구하는 문제입니다.

N = 10000, M = 500이므로 브루트포스를 사용하여 M * N * logN 시간으로 해결할 수 있습니다.

저는 l ~ r 범위의 수들을 벡터에 넣어줬고, 벡터를 오름차순으로 정렬하여 idx번째 수를 출력하는 방식으로 해결했습니다.

 

코드

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
#include <iostream>
#include <vector>
#include <algorithm>
#define MAX 10001
using namespace std;
 
int list[MAX];
int N, M;
 
void func() {
    int l, r, idx;
    while (M--) {
        cin >> l >> r >> idx;
        vector<int> tmp;
        for (int i = l; i <= r; i++) {
            tmp.push_back(list[i]);
        }
        sort(tmp.begin(), tmp.end());
        cout << tmp[idx - 1<< '\n';
        tmp.clear();
    }
}
 
void input() {
    int x;
    cin >> N >> M;
    for (int i = 1; i <= N; i++) {
        cin >> list[i];
    }
}
 
int main() {
    cin.tie(NULL); cout.tie(NULL);
    ios::sync_with_stdio(false);
 
    input();
    func();
 
    return 0;
}
cs

문제

 

풀이

입력에서 주어지는 N과 숫자의 구성이 같으면서 더 큰수 라고 한다면 순열 조합 문제라는 것을 떠올리실 수 있습니다.

숫자 구성을 모두 사용해야 하므로 이 문제는 순열입니다.

 

정답이 반드시 있는 경우만 입력으로 주어지므로 54321 처럼 다음 순열이 없는 경우는 없습니다.

따라서 예외 처리 없이 다음 순열을 구해주시기만 하면 됩니다.

C++에는 algorithm 라이브러리에서 next_permutation 메소드를 지원해주기 때문에 편하게 해결할 수 있었습니다.

 

코드

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
#include <iostream>
#include <string>
#include <algorithm>
using namespace std;
 
string N;
 
void func() {
    next_permutation(N.begin(), N.end());
    cout << N << '\n';
}
 
void input() {
    cin >> N;
}
 
int main() {
    cin.tie(NULL); cout.tie(NULL);
    ios::sync_with_stdio(false);
 
    input();
    func();
 
    return 0;
}
cs

https://code-challenge.elice.io/courses/95930/info

 

알고리즘 코드 챌린지 예선 도전하기 | 엘리스 코드 챌린지

 

code-challenge.elice.io

엘리스에서 알고리즘 챌린지를 개최했다. 나는 지인의 추천을 받아서 알게되었고, 1의 망설임도 없이 참여했다.

 

우선 진행 방식은 예선 / 본선으로 나뉘고,

예선은 평일에 온라인으로 2주간 진행되며, 매일 1문제씩 나오는 것을 풀기만 하면 된다.

그리고 함께 공개되는 강의를 수강할 수 있다.

예선 성적 상위 50명이 오프라인 본선으로 진출하게 된다.

 

예선 1주차

1주차라서 그런지 백준 난이도 실버3 ~ 골드4 정도의 쉬운 문제들이 출제되었다.

문제 뜻을 이해하지 못해서 꽤 시간이 걸린 문제도 있었지만 당일에 모두 AC를 받았다.

로직 자체도 어렵지 않고 무난하게 했던것 같다.

이렇게되면 본선 진출자 선발을 어떻게 하려나 라는 생각을 했다.

 

예선 2주차

1주차가 왜 쉬웠는지 6일차부터 말해주는것 같았다.

2가지 방법으로 풀었고, 이건 반례가 있을 수 없다고 확신했기 때문에 더 충격이었던것 같다.

근데 아무리 수정하고 제출해도 60점만 나왔고, 내 접근 방법은 틀렸다가 결론이 되었다.

거의 하루종일 다른 방법 찾기에 매달려있었고, 시간을 넘겨서 60점으로 마무리했다.

이날 정답자 100명을 종료 30분 전에 채운만큼 난이도가 있었던 것으로 보인다. (다른 날은 저녁 전에는 100명을 채웠던 걸로 기억)

 

그리고 7일차는 필기만 끄적이다가 솔루션을 찾지 못했다.

보통 애드혹 문제는 풀이를 보면 "와 이생각을 못했네" 라는 생각이 바로 드는데 이 문제가 그랬다.

 

8일차는 dp가 나왔다.

내가 dp를 제일 좋아해서 그런지 문제를 보자마자 dp인것 같았다.

알고리즘 문제를 풀 때는 문제를 잘 읽어야 한다. 문제에서 요구하는 조건들을 잘 파악해야 한다.

근데 그러지 못했고, 그게 정답을 맞추지 못하는 결정적인 역할을 했다.

이거 때문에 몇 시간은 더 걸려서 풀었다.

 

9일차도 틀린 방법으로 접근했다.

반례는 찾지 못했지만 내 방법이 틀렸다는 것을 저녁에서야 깨달았고, 두번째 풀때는 생각보다 간단하게 풀어서 당황했던 기억이 있다.

AC를 받자마자 이 문제를 이렇게까지 복잡하게 생각했구나 라는 생각이 바로 들었다.

 

10일차는 대놓고 어렵게 나왔다.

생각한 알고리즘으로는

1. Parametric Search + Segment tree

2. Prefix sum + Sliding Window

이정도로 생각했고, 하루종일 로직을 그렸는데 답은 찾지 못했다.

0시가 되자마자 풀이를 확인했고, 내가 생각한 1번 방법과 비슷했지만 코드 이해가 되지않아서 포기했다.

혹시나 솔루션을 올리신 분이 있으신가 찾아봤더니 생각보다 좋은 아이디어를 얻게되어 다음날에는 AC를 받을 수 있었다.

 

2주차 난이도는 백준 골드3 ~ 플레3 정도였던 것 같다.

그래도 2개는 맞췄으니 다행이지 않을까.... 아마............ ㅠ

 

마무리

매번 뭔가를 할때마다 아쉬움은 남는것 같다.

대회를 조금 가볍게 생각해서인지 실수가 많이 나왔었고, 그거 때문에 더 좋은 점수를 받지 못한게 아닌가 생각을 해본다.

저기 사진에 있는 제출 횟수가 부끄럽긴 하다.. ㅎㅎ 세상에 문제를 제대로 못읽지를 않나..

 

문제 중에는 백준과 비슷한 문제가 몇개 섞여있었고, 가독성이 떨어지는 문제들도 있었다.

그 부분은 분명 아쉽겠지만 새롭게 알아가는 것도 많았고, 이것 또한 경험이라고 생각한다.

본선은 넥터 24기 세션 장소 중 하나였던 엘리스랩에서 진행되는데 한번더 못가게 된것도 아쉽긴 한데 잘했어야지 ^^

그래도 1일 1백준을 하기 때문에 이정도라도 하지 않았을까 라며 긍정회로를 돌려본다. 아무튼 재밌었으니까 됐지!

 

대회를 여러번 참여하긴 했지만 엘리스 코드 챌린지처럼 장기간동안 하는 대회는 처음인것 같다.

이 문제들이 공유가 가능하다면 하나씩 포스팅해볼 생각이지만 아마 안되겠지..?

다음에도 이런 기회가 있다면 다시 도전해볼 생각이다. 그때는 부족한점을 더 보완할 수 있도록 지금처럼 꾸준히 문제를 풀어야겠다.

'잡담' 카테고리의 다른 글

2024년 8월 회고  (2) 2024.09.04
2024년 7월 회고  (2) 2024.08.01
6월 회고  (0) 2024.07.01
Nexters 24기 회고  (2) 2024.06.16
5월 회고  (1) 2024.06.07

+ Recent posts