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

 

회의는 한번에 하나만 진행이 가능하며, 주어지는 N개의 회의들을 적절하게 배정하여 최대 인원을 구하는 문제입니다.

저는 dp + parametric search로 접근했습니다.

dp[N]: N번째 회의부터 모을 수 있는 최대 인원

 

우선 회의들을 시작시간 기준 오름차순으로 정렬합니다.

그리고 해당 회의를 진행하거나, 진행하지 않거나 두가지를 모두 확인할 수 있습니다.

 

이번 회의를 넘긴다면 바로 idx + 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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
#include <iostream>
#include <algorithm>
#include <cstring>
#define MAX 100001
using namespace std;
 
typedef struct Node {
    int l, r, s;
}Node;
 
Node list[MAX];
int dp[MAX];
int N;
 
bool cmp(Node a, Node b) {
    return a.l < b.l;
}
 
int bs(int x) {
    int l = 0;
    int r = N;
    while (l < r) {
        int m = (l + r) >> 1;
        if (list[m].l >= x) r = m;
        else l = m + 1;
    }
    return l;
}
 
int solve(int idx) {
    if (idx >= N) return 0;
 
    int& ret = dp[idx];
    if (ret != -1return ret;
 
    return ret = max(solve(idx + 1), list[idx].s + solve(bs(list[idx].r)));
}
 
void func() {
    sort(list, list + N, cmp);
    memset(dp, -1sizeof(dp));
    cout << solve(0<< '\n';
}
 
void input() {
    cin >> N;
    for (int i = 0; i < N; i++) {
        cin >> list[i].l >> list[i].r >> list[i].s;
    }
}
 
int main() {
    cin.tie(NULL); cout.tie(NULL);
    ios::sync_with_stdio(false);
 
    input();
    func();
 
    return 0;
}
cs

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

boj 30461 낚시  (0) 2024.07.28
boj 17258 인기가 넘쳐흘러  (0) 2024.07.20
boj 1398 동전 문제  (0) 2024.07.04
boj 1229 육각수  (0) 2024.06.30
boj 25378 조약돌  (0) 2024.06.29

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

 

점화식만 구한다면 쉽게 풀리는 문제입니다.

2차원 배열의 누적합이지만 기존 방식처럼 (1, 1) ~ (x, y)의 모든 값을 더하는 것이 아닌 대각선 범위에 있는 수들을 더해야합니다.

 

우선 본인보다 위에 있는 수는 포함되어야 합니다. 그러면 dp[i - 1][j]를 추가합니다.

다음은 본인보다 대각선 위에 있는 수도 포함되어야 합니다. 그러면 dp[i - 1][j - 1]가 추가됩니다.

 

여기서 하나 생각해야할건 dp[i - 1][j]에는 dp[i - 2][j - 1]이 포함되어 있습니다. (대각선)

그리고 dp[i - 1][j - 1]에도 dp[i - 2][j - 1]가 포함되어 있습니다. (위)

그러니 i > 1인 좌표에서는 dp[i - 2][j - 1]를 한번 빼줘야 점화식이 완성됩니다.

 

그러면 최종 점화식은

dp[i][j] = dp[i - 1][j] + dp[i - 1][j - 1] - dp[i - 2][j - 1]

이렇게 됩니다!

점화식을 구했으니 이제 입력이 들어오면 바로 dp[x][y]를 출력해주시면 되겠습니다.

 

 

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>
#define MAX 2001
using namespace std;
 
int dp[MAX][MAX];
int N, M, Q;
 
void func() {
    int x, y;
    while (Q--) {
        cin >> x >> y;
        cout << dp[x][y] << '\n';
    }
}
 
void input() {
    cin >> N >> M >> Q;
    for (int i = 1; i <= N; i++) {
        for (int j = 1; j <= M; j++) {
            cin >> dp[i][j];
            dp[i][j] += (dp[i - 1][j] + dp[i - 1][j - 1]);
            if (i > 1) {
                dp[i][j] -= dp[i - 2][j - 1];
            }
        }
    }
}
 
int main() {
    cin.tie(nullptr); cout.tie(nullptr);
    ios::sync_with_stdio(false);
 
    input();
    func();
 
    return 0;
}
cs

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

boj 19623 회의실 배정 4  (0) 2024.07.29
boj 17258 인기가 넘쳐흘러  (0) 2024.07.20
boj 1398 동전 문제  (0) 2024.07.04
boj 1229 육각수  (0) 2024.06.30
boj 25378 조약돌  (0) 2024.06.29

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

 

순서쌍 (x, y)는 학생y 가 학생x 보다 뒤에 있으면서 더 작은 번호를 가지고 있다는 것을 의미하며,

입력으로 주어지는 순서쌍을 제외하면 더 작은 번호를 갖고있는 학생은 무조건 앞에 있다는 뜻이 됩니다.

 

그러면 최초 입력을 받기 전에는 1 ~ N번째 학생은 1 ~ N번을 순서대로 들고있게 됩니다.

그리고 입력되는 (x, y)에서 x는 본인보다 뒤에 있는 y보다 큰 수를 갖고 있으므로 +1으로 카운팅할 수 있습니다.

반대로 y는 -1으로 카운팅할 수 있습니다.

 

그렇게 수정된 배열에는 학생들이 들고있는 번호가 완성되지만 중복이 포함될 수 있습니다.

그런 경우에는 -1을 출력해주시고, 그렇지 않다면 1 ~ N번 학생의 번호를 순서대로 출력해줍니다.

 

 

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
#include <iostream>
#define MAX 100001
using namespace std;
 
int list[MAX];
bool chk[MAX];
int N, M;
 
void init() {
    for (int i = 1; i <= N; i++) {
        list[i] = i;
    }
}
 
void func() {
    for (int i = 1; i <= N; i++) {
        if (chk[list[i]]) {
            cout << "-1\n";
            return;
        }
        chk[list[i]] = true;
    }
 
    for (int i = 1; i <= N; i++) {
        cout << list[i] << ' ';
    }
    cout << '\n';
}
 
void input() {
    int x, y;
    cin >> N >> M;
    init();
    while (M--) {
        cin >> x >> y;
        list[x]++;
        list[y]--;
    }
}
 
int main() {
    cin.tie(NULL); cout.tie(NULL);
    ios::sync_with_stdio(false);
 
    input();
    func();
 
    return 0;
}
cs

'algorithm > ad-hoc' 카테고리의 다른 글

boj 10350 Banks  (0) 2024.07.31
boj 5527 전구 장식  (0) 2024.07.30
boj 2381 최대 거리  (0) 2024.07.25
boj 14675 단절점과 단절선  (0) 2024.07.23
boj 27468 2배 또는 0.5배  (0) 2024.07.22

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

 

두 점의 좌표 (a, b), (c, d)가 있을 때 L1-metric 거리는 |a - c| + |b - d| 이라고 하고 L1-metric의 최댓값을 구하는 문제입니다.

우선 절댓값부터 전개를 하면

1. (a + b) - (c + d)

2. - (a + b) + (c + d)

3. (a - b) - (c - d)

4. - (a - b) + (c - d)

이렇게 되는것을 알 수 있습니다.

 

여기서 (1, 2), (3, 4)를 묶어서 보면 각각 x + y, x - y의 차이를 계산하고 있습니다.

그렇다면 x + y, x - y를 배열에 각각 넣고 정렬해서 최대 - 최소를 구한다면 답을 구할 수 있습니다.

이 때 x + y의 최대 - 최소, x - y의 최대 - 최소 중 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
#include <iostream>
#include <algorithm>
#define MAX 50001
using namespace std;
 
int list[2][MAX];
int N;
 
void func() {
    sort(list[0], list[0+ N);
    sort(list[1], list[1+ N);
    cout << max(list[0][N - 1- list[0][0], list[1][N - 1- list[1][0]) << '\n';
}
 
void input() {
    int x, y;
    cin >> N;
    for (int i = 0; i < N; i++) {
        cin >> x >> y;
        list[0][i] = x + y;
        list[1][i] = x - y;
    }
}
 
int main() {
    cin.tie(NULL); cout.tie(NULL);
    ios::sync_with_stdio(false);
 
    input();
    func();
 
    return 0;
}
cs

'algorithm > ad-hoc' 카테고리의 다른 글

boj 5527 전구 장식  (0) 2024.07.30
boj 14864 줄서기  (0) 2024.07.26
boj 14675 단절점과 단절선  (0) 2024.07.23
boj 27468 2배 또는 0.5배  (0) 2024.07.22
boj 1377 버블 소트  (0) 2024.06.07

문제

 

풀이

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/10453

 

문제 키워드에 애드혹이 있던데 잘은 모르겠습니다.

제가 이 로직으로 접근하기 위해 생각한것 자체가 애드혹이 될 수는 있겠네요!

 

1. ab 는 좋은 문자열이다.
2. 만약 문자열 [S]가 좋은 문자열이라면, 오른쪽과 왼쪽 끝에 각각 a와 b를 추가한 문자열 a[S]b 또한 좋은 문자열이다.
3. 만약 문자열 [S]와 [T]가 좋은 문자열이라면 이들을 붙여 쓴 [S][T] 또한 좋은 문자열이다.

좋은 문자열의 정의가 이렇게 나옵니다.

그리고 입력으로 좋은 문자열 A, B가 주어지고, A -> B의 최소 연산 횟수를 구해야합니다.

 

우선 위 정의를 보고 알 수 있는건 "두 문자열의 구성은 같다" 입니다.

ab = S는 좋은 문자열이며, aSb도 좋은 문자열입니다. 그리고 SS도 좋은 문자열입니다.

여기서 사용된 a과 b의 갯수는 같습니다.

 

그렇다면 생각해볼 예외는 "두 문자열의 길이가 다른 경우" 입니다.

두 문자열의 길이가 다르다면 당연히 A -> B가 성립하지 않겠죠.

 

문제의 로직은

a, b의 갯수가 둘다 동일하기 때문에 b의 위치를 각각 찾습니다.

그리고 b 위치의 차이를 더합니다.

b의 갯수도 당연히 똑같기 때문에 하나라도 문자열의 길이를 벗어나면 종료할 수 있습니다.

 

 

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>
using namespace std;
 
string x, y;
int len1, len2;
 
void func() {
    if (len1 != len2) {
        cout << "-1\m";
        return;
    }
 
    int ret = 0;
    int idx1 = 0;
    int idx2 = 0;
    while (1) {
        while (idx1 < len1 && x[idx1] == 'a') idx1++;
        while (idx2 < len2 && y[idx2] == 'a') idx2++;
        if (idx1 >= len1 || idx2 >= len2) break;
        ret += abs(idx1++ - idx2++);
    }
    cout << ret << '\n';
}
 
void input() {
    cin >> x >> y;
    len1 = x.size();
    len2 = y.size();
}
 
int main() {
    cin.tie(NULL); cout.tie(NULL);
    ios::sync_with_stdio(false);
 
    int tc;
    cin >> tc;
    while (tc--) {
        input();
        func();
    }
 
    return 0;
}
cs

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

boj 9081 단어 맞추기  (0) 2021.12.15
boj 10096 세 친구  (0) 2021.01.29

문제

 

풀이

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

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

 

단절점은 지웠을 때 그래프가 2개 이상으로 나눠지는 정점이라고 하고,

단절선은 지웠을 때 그래프가 2개 이상으로 나눠지는 간선이라고 합니다.

 

이 문제는 트리라고 명시되어 있기 때문에 애드혹으로 접근할 수 있습니다.

트리는 N - 1개의 간선만으로 N개의 정점을 연결하는 그래프입니다.

즉, 그래프에서 간선을 최소 갯수로 사용한게 트리입니다.

 

그렇다면 간선이 하나라도 없어진다면 더이상 하나의 그래프가 될 수 없습니다.

그러면 type == 2일 경우는 모두 yes를 출력하는걸로 마무리할 수 있습니다.

 

그 다음은 단절점인데

간선이 1개인 정점을 지우게 되더라도 여전히 하나의 그래프가 남게됩니다.

간선이 2개 이상인 정점을 지우게 되면 그 간선 만큼 2개 이상의 그래프로 나뉘는것을 알 수 있습니다.

 

그러면 정점을 지우면 해당 정점에 연결된 간선만큼 그래프가 나뉜다는 것을 알 수 있고,

type == 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
#include <iostream>
#include <vector>
#define MAX 100001
using namespace std;
 
int cnt[MAX];
int N, M;
 
void func() {
    int type, x;
    while (M--) {
        cin >> type >> x;
        if (type == 1) {
            if (cnt[x] > 1cout << "yes\n";
            else cout << "no\n";
        }
        else cout << "yes\n";
    }
}
 
void input() {
    int u, v;
    cin >> N;
    for (int i = 1; i < N; i++) {
        cin >> u >> v;
        cnt[u]++;
        cnt[v]++;
    }
    cin >> M;
}
 
int main() {
    cin.tie(NULL); cout.tie(NULL);
    ios::sync_with_stdio(false);
 
    input();
    func();
 
    return 0;
}
cs

'algorithm > ad-hoc' 카테고리의 다른 글

boj 5527 전구 장식  (0) 2024.07.30
boj 14864 줄서기  (0) 2024.07.26
boj 2381 최대 거리  (0) 2024.07.25
boj 27468 2배 또는 0.5배  (0) 2024.07.22
boj 1377 버블 소트  (0) 2024.06.07

문제

 

풀이

이 문제는 방법이 다양한것 같았습니다. 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

+ Recent posts