알고리즘 문제풀이

[알고스팟] 졸업학기 문제풀이

도리컴 2023. 2. 7. 00:44
반응형

코드를 더 깔끔하게 정리할 수 있지만, 일단 처음 풀었을 때는 이렇게 해결했음(풀이개념은 동일)

 ㅇ
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
111
112
113
114
115
116
117
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
 
/*
    <문제요약>
    졸업 필수학점 채우려면 N개 과목 중 K개 이상 수강해야 함
    각 과목은 선수과목이 존재
    매 학기마다 개설되는 과목이 다름
    한 학기당 최대 수강가능한 과목 수가 L개임
    다녀야할 최소 학기 구하기(M학기 넘어가면 IMPOSSIBLE)
 
 
    <입력>
    C(테스트케이스 - <=50)
    N(전공과목 수 - 0~12) K(들어야 할 과목 수 - 0~N) M(학기 수 - 1~10) L(태우가 한 학기에 최대 수강가능한 과목 수 - 1~10)
 
    이후 N줄 - 각 과목의 정보
    Ri(해당과목의 선수과목 수 - 0~N-1) a b c(선수과목의 번호 - R1 개 주어짐)
 
    이후 M줄 - 각 학기 정보
    Ci(해당 학기에 개설되는 과목 수 - 1~10) a b c(개설되는 과목 번호 - Ci 개 주어짐)
 
    <출력>
    각 테스트케이스 별 한 줄에 태우가 다녀야 할 최소학기 수 출력
    M학기 내 졸업할 수 없는 경우, IMPOSSIBLE 출력
 
    <풀이>
    메모이제이션을 이용하는 것이 포인트 + 비트연산 할 줄 알아야함
    graduate(a, b) -> a는 현재학기, b는 수강과목목록일 때 / 이 경우 다녀야하는 최소학기 수를 반환 -> cache에 해당 값 저장 후 재귀함수 때 이용
    각 테스트케이스마다 초기화해야 하는 변수들 제대로 초기화 안해서 계속 오답떴었음..(괜히 엄한 곳에서 오랫동안 수정함)
    cstring 헤더 써서 memset함수로 풀이 한 것들이 대부분임
    */
 
vector<int> prelearn;    //[i]번 과목당 선수과목 표시
vector<int> semester;    //각 학기별 개설과목 표시
int C;
int N, K, M, L;
 
int cache[10][1 << 12];    //cache[a][b] : a는 학기 수, b는 수강한 과목들 -> 이 때의 최소학기
 
int bitCount(int n) {    //해당 수의 이진수 형태에서 1의 개수를 반환
    if (n == 0)return 0;
    return n % 2 + bitCount(n / 2);
}
 
//현재 학기, 수강과목들 -> 이 때의 최소학기 수 반환
int graduate(int cnt, int learned) {    //휴학을 한 경우와 하지 않은 경우 중 최소인 경우의 학기를 반환
 
    if (bitCount(learned) >= K) return 0;
    if (cnt == M)  return 987654321;
    int& ret = cache[cnt][learned];
    if (ret != -1return ret;
    ret = 987654321;
 
    int tmp = ~learned;    //~learned 계산
    int canlearn = semester[cnt] & tmp;    //이번학기 수강가능한 과목들 -> 이번학기 수강가능 && 수강안한과목
 
    for (int i = 0; i < N; i++) {    //각 과목마다
        if ((canlearn & (1 << i)) && ((prelearn[i] & learned) != prelearn[i]))    //canlearn의 각 선수과목 수강여부 확인해서 거르기
            canlearn &= ~(1 << i);
    }
 
    for (int i = canlearn; i > 0; i = ((i - 1& canlearn)) {    //canlearn에서 선택해서 수강하는 경우 모두 탐색
        if (bitCount(i) > L) continue;
        ret = min(ret, graduate(cnt + 1, learned | i) + 1);    //이번학기 카운트하고, 다음학기 계산했을 때 최소학기 수 더함
    }
    ret = min(ret, graduate(cnt + 1, learned));    //휴학했을 경우와 비교
    return ret;
}
 
 
 
int main() {
    cin >> C;
    for (int i = 0; i < C; i++) {    //testcase 반복
 
        for (int ii = 0; ii < 10; ii++//cache 모두 -1로 초기화, prelearn과 semester 초기화
            for (int jj = 0; jj < (1 << 12); jj++) {
                cache[ii][jj] = -1;
                prelearn.erase(prelearn.begin(), prelearn.end());
                semester.erase(semester.begin(), semester.end());
            }
 
 
        cin >> N >> K >> M >> L;    //전공과목 수, 들어야하는 과목 수, 학기 수, 학기당 수강가능 과목 수
 
        for (int i = 0; i < N; i++) {    //각 과목정보 입력
            prelearn.push_back(0);
            int Ri; cin >> Ri;    //해당 과목의 선수과목 수
            for (int j = 0; j < Ri; j++) {
                int a; cin >> a;    //해당 과목의 선수과목 번호
                prelearn[i] |= (1 << a);
            }
        }
 
        for (int i = 0; i < M; i++) {    //각 학기정보 입력
            int Ci; cin >> Ci;    //해당학기 개설과목 수
            semester.push_back(0);
            for (int j = 0; j < Ci; j++) {
                int a; cin >> a;    //해당학기 개설과목 번호
                semester[i] |= (1 << a);
            }
        }
        //각종 입력 및 prelearn, semester에 선수과목, 학기정보 저장 완료
 
        int cnt = 0;    //현재 학기
        int learned = 0;    //수강완료과목 목록
 
        int ans = graduate(cnt, learned);
 
        if (ans == 987654321cout << "IMPOSSIBLE" << endl;
        else cout << ans << endl;
    }
 
}
cs

 

반응형