3년전에 대학 과제로 좌표 압축 기법을 활용하는 문제가 나왔었지만 이해를 못해서 풀지 못했습니다.

지금와서 보니 생각보다 간단했고, 정리하면서 포스팅합니다.

 

우선 좌표압축이란, 수의 범위가 크게 주어질 때 인덱스를 이용하여 범위를 줄이는 기법입니다.

코테에서 요구하는 기법은 아닐지라도 대회 준비를 하셨던 분이라면 보신 적이 있을 거라고 생각합니다.

그만큼 PS에서 많이 사용되는 기법이며, 한 번쯤 알아가는 것도 괜찮은 것 같습니다.

 

1차원 배열

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

 

18870번: 좌표 압축

수직선 위에 N개의 좌표 X1, X2, ..., XN이 있다. 이 좌표에 좌표 압축을 적용하려고 한다. Xi를 좌표 압축한 결과 X'i의 값은 Xi > Xj를 만족하는 서로 다른 좌표의 개수와 같아야 한다. X1, X2, ..., XN에 좌

www.acmicpc.net

이 문제가 가장 기본적으로 좌표 압축을 체험해볼 수 있는 문제입니다.

1차원 좌표를 압축하는 것이 문제에서 요구하는 것인데, 좌표의 범위가 -10억 ~ 10억입니다.

이 범위를 한번씩 돌며 순위를 부여하는데 너무 많은 시간이 필요합니다.

이를 해결하기 위한 기법이 좌표 압축입니다.

좌표의 범위는 -10억 ~ 10억이지만 N의 범위는 1 ~ 100만인 것을 이용합니다.

 

로직의 진행 순서입니다.

  1. 우선 입력을 받을 때, 원래 배열 순서를 유지해야 하므로 인덱스도 함께 저장합니다.
  2. 배열을 좌표를 기준으로 오름차순 정렬합니다.
  3. 이전 값을 사용하기 위한 pre와 압축된 값을 위한 cnt 변수를 선언, 초기화합니다.
  4. N만큼 for문을 돌면서 pre와 list[i] 값을 비교합니다.
  5. pre == list[i]이면 이전에 등장했던 수가 있으므로 cnt를 유지합니다.
  6. pre != list[i]이면 다른 수가 등장했으므로 cnt를 1 증가합니다.
  7. pre를 현재 좌표값으로 갱신하고, list[i]에 cnt를 넣어줍니다.
  8. 압축된 배열을 원래 배열 순서로 돌리기 위해 1번에서 저장했던 인덱스를 기준으로 오름차순 정렬합니다.

이렇게 되면

2 4 -10 4 -9

이었던 좌표가

2 3 0 3 1

이렇게 변환되는 것을 볼 수 있습니다.

 

 

이 방법 외에 unique를 이용하는 방법도 있습니다.

두 개의 벡터를 사용하며, 하나는 원래 벡터, 하나는 정렬하여 중복을 제거한 벡터로 사용됩니다.

중복을 제거한 후 lower_bound를 적용하면 원래 벡터에서 값의 순서를 알 수 있습니다.

 

원래 벡터를 w, 중복을 제거한 벡터를 v라고 했을 때,

로직의 진행 순서로는

  1. 입력 받은 값을 벡터 2개에 같이 넣어줍니다.
  2. 벡터 v를 오름차순으로 정렬합니다.
  3. erase와 unique를 이용하여 정렬된 벡터 v에서 중복을 제거합니다.
  4. N만큼 for문을 돌면서 lower_bound를 이용하여 w[i] 값의 압축된 값을 가져옵니다.

첫 번째 방법
두 번째 방법

간단해 보이지만 위의 문제에서는 첫 번째 방법이 메모리, 시간 부분에서 효율적인 것을 알 수 있습니다.

 

간단하게 소스도 첨부합니다.

 

첫 번째 방법

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
#include <iostream>
#include <algorithm>
#define MAX 1000000
using namespace std;
typedef pair<intint> pii;
 
pii list[MAX];
int N;
 
void func() {
    sort(list, list + N);
 
    int pre = 1e9 + 1;
    int cnt = -1;
    for (int i = 0; i < N; i++) {
        if (pre != list[i].first) cnt++;
        pre = list[i].first;
        list[i].first = cnt;
    }
    sort(list, list + N, [](pii a, pii b) {
        return a.second < b.second;
    });
 
    for (int i = 0; i < N; i++) {
        cout << list[i].first << ' ';
    }
    cout << '\n';
}
 
void input() {
    cin >> N;
    for (int i = 0; i < N; i++) {
        cin >> list[i].first;
        list[i].second = i;
    }
}
 
int main() {
    cin.tie(NULL); cout.tie(NULL);
    ios::sync_with_stdio(false);
 
    input();
    func();
 
    return 0;
}
cs

 

두 번째 방법

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
#include <iostream>
#include <algorithm>
#include <vector>
#define MAX 1000000
using namespace std;
 
vector<int> v, w;
int N;
 
void func() {
    sort(v.begin(), v.end());
    v.erase(unique(v.begin(), v.end()), v.end());
 
    for (int i = 0; i < N; i++) {
        cout << lower_bound(v.begin(), v.end(), w[i]) - v.begin() << ' ';
    }
    cout << '\n';
}
 
void input() {
    int x;
    cin >> N;
    for (int i = 0; i < N; i++) {
        cin >> x;
        v.push_back(x);
        w.push_back(x);
    }
}
 
int main() {
    cin.tie(NULL); cout.tie(NULL);
    ios::sync_with_stdio(false);
 
    input();
    func();
 
    return 0;
}
cs

 

2차원 배열

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

 

10167번: 금광

첫 줄에는 금광들의 개수 N (1 ≤ N ≤ 3,000)이 주어진다. 이어지는 N개의 줄 각각에는 금광의 좌표 (x, y)를 나타내는 음이 아닌 두 정수 x와 y(0 ≤ x, y ≤ 109), 그리고 금광을 개발하면 얻게 되는 이

www.acmicpc.net

이 문제는 좌표 압축만을 요구하지는 않지만 2차원 배열 좌표압축을 해야하는 문제입니다.

세그먼트 트리처럼 구간 쿼리를 사용하는 문제에 좌표 압축을 적용할 수 있어 이 문제를 가져왔습니다.

전체 풀이는 추후 포스팅할 예정이고, 좌표 압축에 해당하는 부분만 진행하려고 합니다.

 

사실 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
void func() {
    sort(list + 1, list + 1 + N, [](Point a, Point b) {
        return a.x < b.x;
    });
 
    int pre = 1e9 + 1;
    int xCnt = 0;
    for (int i = 1; i <= N; i++) {
        if (pre != list[i].x) xCnt++;
        pre = list[i].x;
        list[i].x = xCnt;
    }
 
    sort(list + 1, list + 1 + N, [](Point a, Point b) {
        return a.y < b.y;
    });
 
    pre = 1e9 + 1;
    int yCnt = 0;
    for (int i = 1; i <= N; i++) {
        if (pre != list[i].y) yCnt++;
        pre = list[i].y;
        list[i].y = yCnt;
    }
}
cs

 

두 번째 방법

1
2
3
4
5
6
7
8
9
10
11
12
void func() {
    sort(xtmp.begin(), xtmp.end());
    xtmp.erase(unique(xtmp.begin(), xtmp.end()), xtmp.end());
 
    sort(ytmp.begin(), ytmp.end());
    ytmp.erase(unique(ytmp.begin(), ytmp.end()), ytmp.end());
 
    for (int i = 1; i <= N; i++) {
        list[i].x = lower_bound(xtmp.begin(), xtmp.end(), list[i].x) - xtmp.begin() + 1;
        list[i].y = lower_bound(ytmp.begin(), ytmp.end(), list[i].y) - ytmp.begin() + 1;
    }
}
cs

 

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

 

12846번: 무서운 아르바이트

성화는 악독하기로 유명한 편의점 사장이다. 그의 편의점에는 특이한 임금 체계를 가지고 있다. 각 날마다 일의 차이때문에 일마다 급여가 정해져 있다. 돈은 당일에 주지 않고 퇴직을 할 때 한

www.acmicpc.net

일을 연속적으로 해야하며, 한 번만 할 수 있습니다.

또한 일을 한 기간동안 가장 작은 일급 * 기간의 돈을 받습니다.

 

가장 작은 일급이 10이고, 5일동안 일해서 받은 50보다

가장 작은 일급이 20이고, 3일동안 일해서 받은 60이 더 많은 이익을 챙길 수 있습니다.

 

이 문제는 히스토그램 문제와 요구하는 것이 같으며,

https://emoney96.tistory.com/139

 

boj 6549 히스토그램에서 가장 큰 직사각형

www.acmicpc.net/problem/6549 6549번: 히스토그램에서 가장 큰 직사각형 입력은 테스트 케이스 여러 개로 이루어져 있다. 각 테스트 케이스는 한 줄로 이루어져 있고, 직사각형의 수 n이 가장 처음으로 주

emoney96.tistory.com

https://emoney96.tistory.com/138

 

boj 2104 부분배열 고르기

www.acmicpc.net/problem/2104 2104번: 부분배열 고르기 크기가 N(1≤N≤100,000)인 1차원 배열 A[1], …, A[N]이 있다. 어떤 i, j(1≤i≤j≤N)에 대한 점수는, (A[i]+…+A[j])×Min{A[i], …, A[j]}가 된다. 즉,..

emoney96.tistory.com

두 문제와 동일한 풀이입니다.

 

트리에는 가장 작은 일급을 가진 인덱스를 저장합니다.

init 함수로 전 구간의 최소 인덱스를 저장합니다.

그리고 partition 함수를 통해 해당 구간에서 가장 작은 값을 가지는 인덱스를 query 함수로 찾은 후 최대 이익을 갱신합니다.

해당 인덱스는 가장 작은 값이므로 제외하고, [s ~ idx - 1] [idx + 1 ~ e] 구간으로 나누어서 같은 과정을 반복합니다.

 

 

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
#include <iostream>
#include <algorithm>
#define MAX 100001
using namespace std;
typedef long long ll;
 
ll tree[MAX * 4], list[MAX], ans;
int N;
 
void init(int node, int s, int e) {
    if (s == e) {
        tree[node] = s;
        return;
    }
 
    int m = (s + e) / 2;
    init(node * 2, s, m);
    init(node * 2 + 1, m + 1, e);
 
    int l = tree[node * 2];
    int r = tree[node * 2 + 1];
    if (list[l] < list[r]) tree[node] = l;
    else tree[node] = r;
 
    return;
}
 
int query(int node, int s, int e, int l, int r) {
    if (l > e || s > r) return -1;
    if (l <= s && e <= r) return tree[node];
 
    int m = (s + e) / 2;
    int lIdx = query(node * 2, s, m, l, r);
    int rIdx = query(node * 2 + 1, m + 1, e, l, r);
 
    if (lIdx == -1) {
        return rIdx;
    }
    else if (rIdx == -1) {
        return lIdx;
    }
    else {
        return list[lIdx] < list[rIdx] ? lIdx : rIdx;
    }
}
 
void partition(int s, int e) {
    if (s > e) return;
 
    int idx = query(11, N, s, e);
    ans = max(ans, list[idx] * (ll)(e - s + 1));
    partition(s, idx - 1);
    partition(idx + 1, e);
}
 
void func() {
    init(11, N);
    partition(1, N);
 
    cout << ans << '\n';
}
 
void input() {
    cin >> N;
    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

'algorithm > SegmentTree' 카테고리의 다른 글

boj 10167 금광  (0) 2022.09.02
boj 16993 연속합과 쿼리  (0) 2022.08.29
boj 1849 순열  (0) 2021.09.08
boj 14719 빗물  (0) 2021.03.15
boj 2304 창고 다각형  (0) 2021.02.25

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

 

2015번: 수들의 합 4

첫째 줄에 정수 N과 K가 주어진다. (1 ≤ N ≤ 200,000, |K| ≤ 2,000,000,000) N과 K 사이에는 빈칸이 하나 있다. 둘째 줄에는 배열 A를 이루는 N개의 정수가 빈 칸을 사이에 두고 A[1], A[2], ..., A[N]의 순서로

www.acmicpc.net

N이 20만이므로 모든 부분합을 직접 구할 수는 없습니다.

기존의 누적합 문제에서 부분합을 구하는 식은 dp[r] - dp[l - 1] = K 입니다.

K는 입력으로 주어지기 때문에 식을 변형하면 dp[r] - K = dp[l - 1]이 되고, 이 식으로 문제에 접근합니다.

 

식을 풀어서 설명하면 dp[r](1 ~ r번 요소의 누적합) - K = dp[l - 1](1 ~ l - 1번 요소의 누적합)

이렇게 되는데, l - 1번을 구할 필요는 없습니다. dp[r] - K와 일치하는 값이 몇 번 나왔는지 세어주면 되는겁니다.

이를 계산하기 위해 map을 사용합니다. 정렬이 필요없으므로 unordred_map을 사용하였습니다.

 

누적합을 구하면서 dp[i] - K 값이 map에 있다면 map[dp[i] - K]만큼 카운트를 증가시킵니다.

그리고 dp[i] 값을 새롭게 map에 넣어주거나 1을 증가시킵니다.

dp[i] 값이 K인 경우는 ans를 따로 증가시켜야 합니다.

 

N = 200000, K = 0이고, 배열의 값이 모두 0이라면 int 범위를 넘으므로 long 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
#include <iostream>
#include <unordered_map>
#define MAX 200001
using namespace std;
typedef long long ll;
 
int dp[MAX];
int N, K;
 
void func() {
    unordered_map<int, ll> m;
    ll ans = 0;
    for (int i = 1; i <= N; i++) {
        dp[i] += dp[i - 1];
        if (dp[i] == K) ans++;
 
        if (m.find(dp[i] - K) != m.end()) {
            ans += m[dp[i] - K];
        }
 
        if (m.find(dp[i]) != m.end()) {
            m[dp[i]]++;
        }
        else {
           m.insert({ dp[i], 1LL });
        }
    }
 
    cout << ans << '\n';
}
 
void input() {
    cin >> N >> K;
    for (int i = 1; i <= N; i++) {
        cin >> dp[i];
    }
}
 
int main() {
    cin.tie(NULL); cout.tie(NULL);
    ios::sync_with_stdio(false);
 
    input();
    func();
 
    return 0;
}
cs

 

'algorithm > dp' 카테고리의 다른 글

boj 14453 Hoof, Paper, Scissors (Silver)  (0) 2022.12.30
boj 2281 데스노트  (0) 2022.10.08
boj 21923 곡예 비행  (0) 2022.06.10
boj 12978 스크루지 민호 2  (0) 2022.06.08
boj 10986 나머지 합  (0) 2022.03.08

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

 

2295번: 세 수의 합

우리가 x번째 수, y번째 수, z번째 수를 더해서 k번째 수를 만들었다라고 하자. 위의 예제에서 2+3+5=10의 경우는 x, y, z, k가 차례로 1, 2, 3, 4가 되며, 최적해의 경우는 2, 3, 4, 5가 된다. k번째 수가 최

www.acmicpc.net

집합 U에 포함된 원소들 중 세 수를 골라 더한 a + b + c = d 값도 집합 U에 포함되는 가장 큰 d를 찾는 문제입니다.

N이 최대 1000개이므로 N^3으로 구하면 시간 초과가 뜨기 때문에 다른 방법을 생각해봅니다.

 

우선 이 문제에서 세 수를 고를 때 중복이 허락됩니다.

즉, U = {2, 3, 5, 10, 15}에서 5를 세 번 골라 5 + 5 + 5 = 15가 될 수 있다는 뜻입니다.

이것을 고려하여 문제에 접근합니다.

 

먼저 위에서 기술한 a + b + c = d를 조금 변형하면 a + b = d - c가 됩니다.

그러면 N^2으로 a + b의 모든 경우를 구할 수 있습니다. 구한 값들은 따로 벡터에 저장해둡니다.

그러면 N^2으로 d - c도 구할 수 있으며, 이분 탐색으로 a + b == d - c인 d를 구한다면

시간은 N^2 * logN으로 줄어들 수 있습니다.

 

물론 upper_bound - lower_bound를 이용하여 갯수를 구하여 풀 수도 있지만 최적의 해를 구하는 문제가 아니고,

정확한 값이 있는지 찾기만 하면 되기 때문에 일반적인 이분 탐색으로도 풀 수 있습니다.

 

d - c를 기준으로 a + b를 탐색 해야하므로 v를 정렬해줍니다.

그 다음 이분 탐색으로 v[m] == dc인 경우를 찾아주고, max인 d를 출력해줍니다.

 

그리고 문제에서 고려한 다른 부분입니다.

1. 이 문제에서 입력은 자연수만 올 수 있으므로 d - c <= 0인 경우는 미리 배제할 수 있습니다.

2. d의 최댓값을 출력해야 하므로 순회하면서 max를 사용해야 하지만 미리 list를 내림차순으로 정렬했으므로 찾는 즉시 list[i]를 출력해주시면 됩니다.

 

물론 이 두 가지를 모두 하지 않아도 AC를 받을 수 있습니다.

첫 번째 코드는 가지치기를 했고, a + b = d - c를 찾는 즉시 출력한 코드입니다. 역시 시간이 가장 빨랐습니다.

두 번째 코드는 가지치기를 하지 않았고, a + b = d - c를 찾는 즉시 출력하였습니다.

세 번째 코드는 가지치기를 했고, a + b = d - c인 모든 경우의 max를 구했습니다.

네 번째 코드는 가지치기를 하지 않았고, a + b = d - c인 모든 경우의 max를 구했습니다.

 

4개의 결과 모두 이 문제에서는 시간 차이가 별로 없을 수 있지만 범위가 커진다면 차이도 분명 커질 것입니다.

만약 테스트나 대회문제를 푼다면 두 가지를 모두 하는 것이 좋습니다.

상황에 맞는 알고리즘을 선택하고, 가지치기의 중요성을 깨닫게 하는 문제였습니다.

 

 

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
#include <iostream>
#include <vector>
#include <algorithm>
#define MAX 1001
using namespace std;
 
vector<int> v;
int list[MAX];
int N;
 
void func() {
    for (int i = 0; i < N; i++) {
        for (int j = 0; j < N; j++) {
            v.push_back(list[i] + list[j]);
        }
    }
    sort(v.begin(), v.end());
 
    int vsize = v.size();
    for (int i = 0; i < N; i++) {
        for (int j = 0; j < N; j++) {
            int dc = list[i] - list[j];
            if (dc <= 0continue;
 
            int l = 0;
            int r = vsize - 1;
            while (l <= r) {
                int m = (l + r) / 2;
 
                if (v[m] == dc) {
                    cout << list[i] << '\n';
                    return;
                }
                else if (v[m] > dc) {
                    r = m - 1;
                }
                else {
                    l = m + 1;
                }
            }
        }
    }
}
 
void input() {
    cin >> N;
    for (int i = 0; i < N; i++) {
        cin >> list[i];
    }
    sort(list, list + N, [](int a, int b) {
        return a > b;
    });
}
 
int main() {
    cin.tie(NULL); cout.tie(NULL);
    ios::sync_with_stdio(false);
 
    input();
    func();
 
    return 0;
}
cs

'algorithm > binarysearch' 카테고리의 다른 글

boj 27977 킥보드로 등교하기  (4) 2023.04.22
boj 18113 그르다 김가놈  (0) 2022.10.09
boj 2110 공유기 설치  (0) 2021.04.13
boj 7453 합이 0인 네 정수  (0) 2021.01.22
boj 2143 두 배열의 합  (0) 2021.01.22

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

 

21923번: 곡예 비행

동헌이는 모형 비행기 조종 대회에 참가하였다. 이 대회에서는 격자 모양의 공간에서 모형 비행기를 조종하여 얻는 비행 점수로 순위를 매긴다. 격자의 각 칸에는 점수가 부여되어 있고, 비행

www.acmicpc.net

상승 비행을 할 때 지나간 칸에 부여된 점수의 합 + 하강 비행을 할 때 지나간 칸에 부여된 점수의 합을 구합니다.

이 문제의 예제 입력 5번을 보시면 상승 비행과 하강 비행 시 좌표가 겹칠 수 있다는 것을 알 수 있습니다.

따라서 비행이 끝났다는 것은 "좌표가 맨 우측 아래이고, 하강 비행 중일 때"가 됩니다.

 

모든 좌표들을 대상으로 상승, 하강 비행을 해야하며, 기본적인 dfs에 중복 방문 체크를 위해 dp를 사용합니다.

dp[x][y][flag] : 좌표 (x, y)에 flag 상태로 도달했을 때 얻을 수 있는 최대 비행 점수

여기서 flag란 상승 비행인지, 하강 비행인지를 나타내며,

flag = 0이면 상승 비행, flag = 1이면 하강 비행입니다.

그리고 모든 좌표에 대해 상승, 하강 비행을 해야하므로 3차원 배열 dp를 사용합니다.

 

이 문제에서 입력은 음수도 주어지므로 dp를 최솟값으로 초기화해줍니다.

그리고 좌측 아래 좌표 (N - 1, 0)을 시작점으로 dfs 순회합니다.

현재 상승 비행 중이라면 현재 좌표에서 하강 비행으로 변경 후 최대 점수를 먼저 구해줍니다.

그 다음 현재 좌표에 대해 상승 / 하강 비행을 구분하여 다음 좌표로 진행해줍니다.

비행이 끝났을 때는 (N - 1, M - 1)에 하강 비행 중인 상태에 도달했을 때가 되겠고, 해당 좌표값인 list[x][y]를 리턴합니다.

다른 경우에는 ret의 max를 구한 후 자신의 좌표를 더한 값을 리턴합니다.

 

단순 상승만 하는 문제라면 간단하게 구할 수 있었지만 하강 비행이 추가되어 생각이 필요한 문제였습니다.

 

 

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
#include <iostream>
#include <algorithm>
#include <cstring>
#define MAX 1001
using namespace std;
 
int list[MAX][MAX], dp[MAX][MAX][2];
int direct[2][2][2= { 
    {{0,1},{-1,0}},
    {{0,1},{1,0}}
};
int N, M;
 
int dfs(int x, int y, int flag) {
    if (x == N - 1 && y == M - 1 && flag) {
        return list[x][y];
    }
 
    int& ret = dp[x][y][flag];
    if (ret != -1e9return ret;
 
    if (!flag) ret = dfs(x, y, !flag);
    for (int d = 0; d < 2; d++) {
        int nx = x + direct[flag][d][0];
        int ny = y + direct[flag][d][1];
 
        if (nx < 0 || ny < 0 || nx >= N || ny >= M) continue;
 
        ret = max(ret, dfs(nx, ny, flag));
    }
 
    ret += list[x][y];
    return ret;
}
 
void func() {
    for (int i = 0; i < MAX; i++) {
        for (int j = 0; j < MAX; j++) {
            for (int k = 0; k < 2; k++) {
                dp[i][j][k] = -1e9;
            }
        }
    }
 
    cout << dfs(N - 100<< '\n';
}
 
void input() {
    cin >> N >> M;
    for (int i = 0; i < N; i++) {
        for (int j = 0; j < M; j++) {
            cin >> list[i][j];
        }
    }
}
 
int main() {
    cin.tie(NULL); cout.tie(NULL);
    ios::sync_with_stdio(false);
 
    input();
    func();
 
    return 0;
}
cs

'algorithm > dp' 카테고리의 다른 글

boj 2281 데스노트  (0) 2022.10.08
boj 2015 수들의 합 4  (0) 2022.08.12
boj 12978 스크루지 민호 2  (0) 2022.06.08
boj 10986 나머지 합  (0) 2022.03.08
boj 14238 출근 기록  (0) 2022.02.04

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

 

12978번: 스크루지 민호 2

구두쇠로 유명한 스크루지 민호가 다스리는 천나라가 있다. 천나라에는 N 개의 도시들이 있는데 각각의 도시들 사이에는 양방향 도로로 이어져 있다. 민호는 도시를 세울 때 최소한의 비용만을

www.acmicpc.net

오랜만에 알고리즘 포스팅입니다.

 

N개의 도시와 N - 1개의 도로인 것으로 보아 트리 문제인 것으로 파악할 수 있으며,

최소 갯수의 경찰서를 세워 모든 도시, 도로를 감시해야 합니다.

 

모든 도시들 대상으로 경찰서를 세우는 경우, 세우지 않는 경우를 모두 구한다면 시간초과가 발생할 것입니다.

따라서 dp를 추가하여 treedp로 접근합니다.

 

dp[v][flag]: 현재 도시가 v이고, 상태가 flag일 때 세워야할 경찰서의 최소 갯수

여기서 상태란, 현재 도시에 경찰서를 세울지 결정하는 변수입니다.

flag = 1 일 때, 해당 도시에 경찰서를 세우고,

flag = 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
53
#include <iostream>
#include <algorithm>
#include <vector>
#include <cstring>
#define MAX 100001
using namespace std;
 
vector<int> graph[MAX];
int dp[MAX][2];
int N;
 
int dfs(int v, int pre, int flag) {
    int& ret = dp[v][flag];
    if (ret != -1return ret;
    ret = flag;
 
    for (auto next : graph[v]) {
        if (pre == next) continue;
        if (flag) {
            ret += min(dfs(next, v, 1), dfs(next, v, 0));
        }
        else {
            ret += dfs(next, v, 1);
        }
    }
 
    return ret;
}
 
void func() {
    memset(dp, -1sizeof(dp));
    cout << min(dfs(101), dfs(100)) << '\n';
}
 
void input() {
    int u, v;
    cin >> N;
    for (int i = 0; i < N - 1; 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

'algorithm > dp' 카테고리의 다른 글

boj 2015 수들의 합 4  (0) 2022.08.12
boj 21923 곡예 비행  (0) 2022.06.10
boj 10986 나머지 합  (0) 2022.03.08
boj 14238 출근 기록  (0) 2022.02.04
boj 3673 나눌 수 있는 부분 수열  (0) 2022.01.31

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

 

16946번: 벽 부수고 이동하기 4

N×M의 행렬로 표현되는 맵이 있다. 맵에서 0은 이동할 수 있는 곳을 나타내고, 1은 이동할 수 없는 벽이 있는 곳을 나타낸다. 한 칸에서 다른 칸으로 이동하려면, 두 칸이 인접해야 한다. 두 칸이

www.acmicpc.net

값이 1인 칸을 0으로 변경했을 때 해당 칸에서 이동할 수 있는 칸의 수를 출력하는 문제입니다.

 

맵의 크기가 1000 * 1000이고, 모든 1의 칸을 대상으로 탐색하기에는 무조건 시간초과라는 생각이 들었고,

생각해낸 방법은 값이 0인 인접한 칸들에 넘버링을 부여한 후 넘버링의 크기를 더하는 방식입니다.

이것은 bfs를 통해 할 수 있습니다.

방문했던 모든 칸에 대해 넘버링과 카운팅을 합니다.

모든 인접한 칸들의 방문을 마쳤다면 해당 넘버링에 카운팅했던 수를 넣습니다.

 

그러면 남은 일은 값이 1인 칸에 대해 4방향 탐색하여 인접한 넘버링의 카운트를 모두 더하는 일입니다.

[boj 16932 모양만들기(링크)]에서 하나의 칸을 변경하여 만들 수 있는 모양의 최대 크기를 구한 적이 있습니다.

이 방식과 동일하게 접근하고, 중복된 칸을 예외처리 하기 위해 set을 사용합니다.

 

 

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
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
#include <iostream>
#include <algorithm>
#include <queue>
#include <set>
#define MAX 1001
#define MOD 10
using namespace std;
typedef pair<intint> pi;
 
char list[MAX][MAX];
bool visit[MAX][MAX];
int num[MAX][MAX], ans[MAX][MAX];
int numSize[MAX * MAX];
int direct[4][2= { {0,1},{1,0},{0,-1},{-1,0} };
int N, M;
 
void func() {
    set<int> s;
    for (int i = 0; i < N; i++) {
        for (int j = 0; j < M; j++) {
            if (list[i][j] == '0'continue;
 
            int sum = 0;
            for (int d = 0; d < 4; d++) {
                int nx = i + direct[d][0];
                int ny = j + direct[d][1];
 
                if (nx < 0 || ny < 0 || nx >= N || ny >= M) continue;
                if (list[nx][ny] != '0'continue;
                if (s.find(num[nx][ny]) != s.end()) continue;
 
                sum += numSize[num[nx][ny]];
                s.insert(num[nx][ny]);
            }
 
            ans[i][j] = (sum + 1) % MOD;
            s.clear();
        }
    }
 
    for (int i = 0; i < N; i++) {
        for (int j = 0; j < M; j++) {
            cout << ans[i][j];
        }
        cout << '\n';
    }
}
 
void bfs(int sx, int sy, int n) {
    queue<pi> q;
    q.push({ sx,sy });
    visit[sx][sy] = true;
    int cnt = 0;
    while (!q.empty()) {
        int x = q.front().first;
        int y = q.front().second;
        q.pop();
        
        cnt++;
        num[x][y] = n;
        for (int i = 0; i < 4; i++) {
            int nx = x + direct[i][0];
            int ny = y + direct[i][1];
            
            if (nx < 0 || ny < 0 || nx >= N || ny >= M) continue;
            if (visit[nx][ny] || list[nx][ny] == '1'continue;
 
            q.push({ nx,ny });
            visit[nx][ny] = true;
        }
    }
 
    numSize[n] = cnt;
}
 
void init() {
    int n = 0;
    for (int i = 0; i < N; i++) {
        for (int j = 0; j < M; j++) {
            if (visit[i][j] || list[i][j] == '1'continue;
 
            bfs(i, j, n++);
        }
    }
}
 
void input() {
    cin >> N >> M;
    for (int i = 0; i < N; i++) {
        cin >> list[i];
    }
}
 
int main() {
    cin.tie(NULL); cout.tie(NULL);
    ios::sync_with_stdio(false);
 
    input();
    init();
    func();
 
    return 0;
}
cs

'algorithm > bfs' 카테고리의 다른 글

boj 14466 소가 길을 건너간 이유 6  (0) 2022.10.13
boj 18112 이진수 게임  (0) 2022.09.16
boj 5547 일루미네이션  (0) 2022.05.15
boj 16932 모양 만들기  (0) 2022.05.14
boj 17244 아맞다우산  (0) 2021.11.25

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

 

5547번: 일루미네이션

첫째 줄에 두 개의 정수 W와 H가 주어진다. (1 ≤ W, H ≤ 100) 다음 H줄에는 상근이네 집의 건물 배치가 주어진다. i+1줄에는 W개의 정수가 공백으로 구분되어 있다. j번째 (1 ≤ j ≤ w) 정수의 좌표는

www.acmicpc.net

 

위 그림의 좌표와 상관없이 저는 (1, 1) ~ (N, M)으로 입력을 받았습니다.

나중에 x의 좌표에 따라 인접하는 좌표들만 구분하면 되는 것입니다.

 

이 문제의 핵심은 건물 외부 체크를 어떻게 하느냐입니다.

왼쪽 건물들을 보시면 내부에도 흰색이 존재합니다. 이거를 잘 걸러내야 합니다.

 

저는 배열의 크기를 (N + 2, M + 2)로 설계하였고,

(1, 1) ~ (N, M)에 건물 정도를 입력 받아 가장자리에 있는 좌표에는 모두 건물 외부가 올 수 있게끔 하였습니다.

(0, 0)이나 (N + 1, M + 1)에 있는 것은 무조건 건물 외부 (흰색) 입니다.

그러면 무조건 흰색이 되는 (0, 0)을 시작으로 bfs를 돌려 건물 외부들을 모두 체크할 수있습니다.

 

이후 건물을 bfs로 순회하여 각 좌표에서 인접한 건물이 몇 개 있는지 세어 줍니다.

이 때, 건물 외부의 흰색은 카운팅을 하지 않으며, 건물 내부의 흰색은 카운팅을 합니다.

그래야 건물 내부에도 조명을 설치하지 않습니다. [위 사진 기준 (2, 3)이 예시입니다.]

 

카운팅이 완료되었으면 해당 좌표에서 조명을 설치할 벽면의 길이를 구합니다.

벽면의 길이는 인접한 건물이 0개 ~ 6개인 경우가 동일하므로 직접 그려보시면 알 수 있습니다.

 

 

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
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
#include <iostream>
#include <algorithm>
#include <queue>
#define MAX 103
#define LEN 6
using namespace std;
typedef pair<intint> pi;
 
int list[MAX][MAX];
bool visit[MAX][MAX], sideChk[MAX][MAX];
int direct[2][6][2= {
    {{-1,-1},{-1,0},{0,1},{1,0},{1,-1},{0,-1}},
    {{0,-1},{-1,0},{-1,1},{0,1},{1,1},{1,0}}
};
int len[7];
int N, M, ans;
 
void sideBfs(int sx, int sy) {
    queue<pi> q;
    q.push({ sx,sy });
    sideChk[sx][sy] = true;
    while (!q.empty()) {
        int x = q.front().first;
        int y = q.front().second;
        q.pop();
 
        int idx = x % 2;
        for (int d = 0; d < 6; d++) {
            int nx = x + direct[idx][d][0];
            int ny = y + direct[idx][d][1];
 
            if (nx < 0 || ny < 0 || nx > N + 1 || ny > M + 1continue;
            if (list[nx][ny] || sideChk[nx][ny]) continue;
 
            q.push({ nx,ny });
            sideChk[nx][ny] = true;
        }
    }
}
 
void bfs(int sx, int sy) {
    queue<pi> q;
    q.push({ sx,sy });
    visit[sx][sy] = true;
    while (!q.empty()) {
        int x = q.front().first;
        int y = q.front().second;
        q.pop();
 
        int cnt = 0;
        int idx = x % 2;
        for (int d = 0; d < 6; d++) {
            int nx = x + direct[idx][d][0];
            int ny = y + direct[idx][d][1];
 
            if (nx <= 0 || ny <= 0 || nx > N + 1 || ny > M + 1continue;
            if (!list[nx][ny]) {
                if (!sideChk[nx][ny]) cnt++;
                continue;
            }
            cnt++;
            if (visit[nx][ny]) continue;
 
            q.push({ nx,ny });
            visit[nx][ny] = true;
        }
 
        ans += len[cnt];
    }
}
 
void func() {
    sideBfs(00);
 
    for (int i = 1; i <= N; i++) {
        for (int j = 1; j <= M; j++) {
            if (!list[i][j] || visit[i][j]) continue;
 
            bfs(i, j);
        }
    }
 
    cout << ans << '\n';
}
 
void init() {
    for (int i = 0; i <= LEN; i++) {
        len[i] = LEN - i;
    }
}
 
void input() {
    cin >> M >> N;
    for (int i = 1; i <= N; i++) {
        for (int j = 1; j <= M; j++) {
            cin >> list[i][j];
        }
    }
}
 
int main() {
    cin.tie(NULL); cout.tie(NULL);
    ios::sync_with_stdio(false);
 
    init();
    input();
    func();
 
    return 0;
}
cs

'algorithm > bfs' 카테고리의 다른 글

boj 18112 이진수 게임  (0) 2022.09.16
boj 16946 벽 부수고 이동하기 4  (0) 2022.05.22
boj 16932 모양 만들기  (0) 2022.05.14
boj 17244 아맞다우산  (0) 2021.11.25
boj 1194 달이 차오른다, 가자.  (0) 2021.04.21

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

 

16932번: 모양 만들기

N×M인 배열에서 모양을 찾으려고 한다. 배열의 각 칸에는 0과 1 중의 하나가 들어있다. 두 칸이 서로 변을 공유할때, 두 칸을 인접하다고 한다. 1이 들어 있는 인접한 칸끼리 연결했을 때, 각각의

www.acmicpc.net

정말 오랜만에 알고리즘 포스팅입니다.

 

이 문제는 배열의 값이 0인 한 칸을 1로 변경하여 만들 수 있는 모양의 최대 크기를 구하는 문제입니다.

이 문제에서 모양이란 배열 내에서 1로 이루어진 연결 요소입니다.

 

저는 bfs를 통해 1로 이루어진 모양들을 넘버링하여 해당 넘버링의 크기를 vector에 넣어주었고,

브루트포스를 통해 값이 0인 칸들의 인접한 칸들의 크기 합을 구해 max를 구하였습니다.

이 때, 인접한 칸에서 같은 넘버링이 나올 수 있으므로 중복체크를 위해 set을 사용하였습니다.

 

넘버링했다는 것은

0 1 1
0 0 1
0 1 0

이렇게 되어있는 배열을

0 1 1
0 0 1
0 2 0

이렇게 모양별로 구분할 수 있도록 변경하였습니다.

이 상태에서 브루트포스로 모양들을 연결해주는 것입니다.

 

여기서 인접한 칸에서 같은 넘버링이 나오는 경우는 좌표 (1, 1)을 보시면 이해하실 수 있습니다.

0 1 1
0 0 1
0 2 0
(1, 1)을 1로 변경한다고 했을 때 위(0, 1)와 오른쪽(1, 2)은 같은 넘버링을 가진 모양입니다.
이미 같은 모양에 속하므로 이를 중복으로 더해서는 안됩니다.
따라서 중복체크를 위해 set을 사용하였습니다.

 

 

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
89
90
91
92
93
94
95
96
97
98
#include <iostream>
#include <queue>
#include <vector>
#include <set>
#define MAX 1001
using namespace std;
typedef pair<intint> pi;
 
int list[MAX][MAX];
bool visit[MAX][MAX];
int direct[4][2= { {0,1},{1,0},{0,-1},{-1,0} };
vector<int> sizeList;
int N, M;
 
void bfs(int sx, int sy, int num) {
    int ret = 0;
    queue<pi> q;
    q.push({ sx,sy });
    visit[sx][sy] = true;
    while (!q.empty()) {
        int x = q.front().first;
        int y = q.front().second;
        q.pop();
 
        ret++;
        list[x][y] = num;
 
        for (int i = 0; i < 4; i++) {
            int nx = x + direct[i][0];
            int ny = y + direct[i][1];
 
            if (nx < 0 || ny < 0 || nx >= N || ny >= M) continue;
            if (!list[nx][ny] || visit[nx][ny]) continue;
 
            q.push({ nx,ny });
            visit[nx][ny] = true;
        }
    }
 
    sizeList.push_back(ret);
}
 
void func() {
    sizeList.push_back(0);
    int num = 1;
    for (int i = 0; i < N; i++) {
        for (int j = 0; j < M; j++) {
            if (!list[i][j] || visit[i][j]) continue;
 
            bfs(i, j, num++);
        }
    }
 
    int ans = 0;
    set<int> s;
    for (int i = 0; i < N; i++) {
        for (int j = 0; j < M; j++) {
            if (list[i][j]) continue;
 
            int ret = 0;
            for (int d = 0; d < 4; d++) {
                int nx = i + direct[d][0];
                int ny = j + direct[d][1];
 
                if (nx < 0 || ny < 0 || nx >= N || ny >= M) continue;
                if (!list[nx][ny]) continue;
                if (s.find(list[nx][ny]) != s.end()) continue;
 
                s.insert(list[nx][ny]);
                ret += sizeList[list[nx][ny]];
            }
 
            ans = max(ans, ret + 1);
            s.clear();
        }
    }
 
    cout << ans << '\n';
}
 
void input() {
    cin >> N >> M;
    for (int i = 0; i < N; i++) {
        for (int j = 0; j < M; j++) {
            cin >> list[i][j];
        }
    }
}
 
int main() {
    cin.tie(NULL); cout.tie(NULL);
    ios::sync_with_stdio(false);
 
    input();
    func();
 
    return 0;
}
cs

'algorithm > bfs' 카테고리의 다른 글

boj 16946 벽 부수고 이동하기 4  (0) 2022.05.22
boj 5547 일루미네이션  (0) 2022.05.15
boj 17244 아맞다우산  (0) 2021.11.25
boj 1194 달이 차오른다, 가자.  (0) 2021.04.21
boj 17471 게리맨더링  (0) 2021.04.16

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

 

14725번: 개미굴

첫 번째 줄은 로봇 개미가 각 층을 따라 내려오면서 알게 된 먹이의 정보 개수 N개가 주어진다.  (1 ≤ N ≤ 1000) 두 번째 줄부터 N+1 번째 줄까지, 각 줄의 시작은 로봇 개미 한마리가 보내준 먹이

www.acmicpc.net

개미굴의 각 저장소들을 트라이 방식으로 저장할 수 있습니다.

A B C 이런식으로 입력이 들어오면 A 다음 B 다음 C 이런식으로 저장하는 방식입니다.

 

다만 들어오는 데이터가 문자열 방식이므로 일반적인 배열로는 할 수 없으며 map를 사용합니다.

m.find(str) 메소드를 이용하여 해당 depth에 문자열이 저장되었는지 확인 후

저장되었다면 다음 depth로 이동, 저장되지 않았다면 해당 depth의 map에 문자열을 저장 후 다음 depth로 이동합니다.

 

출력은 개미굴의 구조를 사전 순으로 출력합니다.

map은 이미 정렬이 된 자료구조이므로 iterator를 이용하여 사전 순으로 출력해줍니다.

 

 

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
#include <iostream>
#include <map>
#include <string>
#define MAX 16
using namespace std;
 
string list[MAX];
int N, M;
 
typedef struct Trie {
    map<string, Trie*> m;
 
    ~Trie() {
        map<string, Trie*>::iterator iter = m.begin();
        for (; iter != m.end(); iter++) {
            delete (*iter).second;
        }
    }
 
    void insert(int idx) {
        if (idx == M) return;
 
        if (m.find(list[idx]) == m.end()) {
            m.insert({ list[idx], new Trie() });
            m[list[idx]]->insert(idx + 1);
        }
        else {
            m[list[idx]]->insert(idx + 1);
        }
    }
 
    void find(int depth) {
        map<string, Trie*>::iterator iter = m.begin();
        for (; iter != m.end(); iter++) {
            for (int i = 0; i < depth; i++) {
                cout << "--";
            }
            cout << (*iter).first << '\n';
 
            (*iter).second->find(depth + 1);
        }
    }
}Trie;
 
Trie *t;
 
void func() {
    t->find(0);
}
 
void init() {
    t = new Trie();
}
 
void input() {
    init();
    cin >> N;
    for (int i = 0; i < N; i++) {
        cin >> M;
        for (int j = 0; j < M; j++) {
            cin >> list[j];
        }
        t->insert(0);
    }
}
 
int main() {
    cin.tie(NULL); cout.tie(NULL);
    ios::sync_with_stdio(false);
 
    input();
    func();
 
    return 0;
}
cs

'algorithm > Trie' 카테고리의 다른 글

boj 5052 전화번호 목록  (0) 2022.03.27

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

 

5052번: 전화번호 목록

첫째 줄에 테스트 케이스의 개수 t가 주어진다. (1 ≤ t ≤ 50) 각 테스트 케이스의 첫째 줄에는 전화번호의 수 n이 주어진다. (1 ≤ n ≤ 10000) 다음 n개의 줄에는 목록에 포함되어 있는 전화번호가

www.acmicpc.net

트라이를 연습하려고 풀었던 문제입니다.

 

트라이에 주어지는 전화번호들을 전부 넣은 다음, 넣었던 전화번호들을 트라이 안에서 검색합니다.

검색하던 도중 해당 전화번호까지 가기 전에 isEnd를 만나면 접두어가 있다는 뜻이므로 NO를 출력합니다.

모든 전화번호를 탐색하여 접두어를 만나지 못하면 YES를 출력합니다.

 

 

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 <string>
#include <algorithm>
#include <cstring>
#define MAX 10001
using namespace std;
 
char list[MAX][11];
int N;
 
int charToInt(char x) {
    return x - '0';
}
 
typedef struct Trie {
    bool isEnd;
    Trie *next[10];
    Trie() : isEnd(false) {
        memset(next, 0sizeof(next));
    }
 
    ~Trie() {
        for (int i = 0; i < 10; i++) {
            if (next[i]) delete next[i];
        }
    }
 
    void insert(char *key) {
        if (*key == '\0') {
            isEnd = true;
        }
        else {
            int idx = charToInt(*key);
            if (!next[idx]) next[idx] = new Trie();
            next[idx]->insert(key + 1);
        }
    }
 
    bool find(char *key) {
        if (*key == '\0'return true;
        if (isEnd) return false;
 
        int idx = charToInt(*key);
        return next[idx]->find(key + 1);
    }
}Trie;
 
Trie *t;
 
void func() {
    for (int i = 0; i < N; i++) {
        if (!(t->find(list[i]))) {
            cout << "NO\n";
            return;
        }
    }
 
    cout << "YES\n";
}
 
void input() {
    t = new Trie();
    cin >> N;
    for (int i = 0; i < N; i++) {
        cin >> list[i];
        t->insert(list[i]);
    }
}
 
void init() {
    delete t;
}
 
int main() {
    cin.tie(NULL); cout.tie(NULL);
    ios::sync_with_stdio(false);
 
    int tc;
    cin >> tc;
    while (tc--) {
        input();
        func();
        init();
    }
 
    return 0;
}
cs

'algorithm > Trie' 카테고리의 다른 글

boj 14725 개미굴  (0) 2022.03.27

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

 

10986번: 나머지 합

수 N개 A1, A2, ..., AN이 주어진다. 이때, 연속된 부분 구간의 합이 M으로 나누어 떨어지는 구간의 개수를 구하는 프로그램을 작성하시오. 즉, Ai + ... + Aj (i ≤ j) 의 합이 M으로 나누어 떨어지는 (i, j)

www.acmicpc.net

[boj 3673 나눌 수 있는 부분 수열] 문제와 같은 풀이 방법입니다.

 

모든 수열의 누적합을 구해 각각의 누적 합의 %M 값을 사용합니다.

1
2
5 3
1 2 3 1 2
cs

위의 입력을 예시로 들면

1
2
sum   = 1 3 6 7 9
MOD M = 1 0 0 1 0
cs

이렇게 됩니다. 저는 이번 문제는 dp에 MOD M한 값을 미리 저장해두었고, cnt[dp[i]]으로 카운팅 하였습니다.

 

M = 3이므로

나머지가 0인 갯수는 3

나머지가 1인 갯수는 2이므로

 

나머지가 같은 부분 수열에서 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
#include <iostream>
#define MAX_N 1000001
#define MAX_M 1001
using namespace std;
typedef long long ll;
 
ll cnt[MAX_M], ans;
int dp[MAX_N];
int N, M;
 
void func() {
    ans = cnt[0];
    for (int i = 0; i < M; i++) {
        ans += (cnt[i] * (cnt[i] - 1/ 2LL);
    }
 
    cout << ans << '\n';
}
 
void input() {
    cin >> N >> M;
    for (int i = 1; i <= N; i++) {
        cin >> dp[i];
 
        dp[i] = (dp[i] + dp[i - 1]) % M;
        cnt[dp[i]]++;
    }
}
 
int main() {
    cin.tie(NULL); cout.tie(NULL);
    ios::sync_with_stdio(false);
 
    input();
    func();
 
    return 0;
}
cs

'algorithm > dp' 카테고리의 다른 글

boj 21923 곡예 비행  (0) 2022.06.10
boj 12978 스크루지 민호 2  (0) 2022.06.08
boj 14238 출근 기록  (0) 2022.02.04
boj 3673 나눌 수 있는 부분 수열  (0) 2022.01.31
boj 20500 Ezreal 여눈부터 가네 ㅈㅈ  (0) 2022.01.01

+ Recent posts