어떻게 풀까?
이 문제를 단순히 반복적 동적 계획법을 이용해서 풀었더니 시간 초과가 나버립니다.
다른 유용한 테크닉을 사용해야하죠!
우선! 이 문제에서 영향을 줄 수 있는 부분을 찾아야 합니다!
노래의 길이는 총 4분 입니다!
그러면, 방금 시작한 노래, 1분 전에 시작한 노래, 2분 시작한 끝난 노래, 3분 전에 시작한 노래가 영향을 줄 수 있겠죠!
4분 전에 시작한 노래는 노래가 끝난 후 다른 노래가 이어지겠죠. 노래를 확인하는게 30초 뒤이기 때문입니다!
이는 결국 '1분 전 시작한 노래'로 넣어야 하는 것입니다.
테스트 케이스에서 가장 긴 노래의 길이를 찾았고, 그 길이가 l이라고 한다면, 방금 시작한 노래, 1분 전에 시작한 노래, ... l-1 분 전에 시작한 노래가 영향을 줄 것 입니다.
또한, 노래의 종류가 n개이기 때문에 각 시작된 노래마다 l 개의 노래가 존재하겠죠!
이를 C(i)라고 합시다!
그렇다면 C(i+1)은 어떻게 구성될까요?
3분 전 시작한 노래는 4분 전 시작한 노래가 되기 때문에 사라져 버릴 것입니다.
2분 전 시작한 노래는 3분 전 시작한 노래가 되고,
1분 전 시작한 노래는 2분 전 시작한 노래가 되겠죠!
그렇다면 가장 중요한 재생 중인 노래는 어떻게 될까요!?
3분 전 시작한 노래를 생각해봅시다.
3분 전에 시작한 노래는 노래 길이가 4일 경우에! 방금 시작한 노래가 될 수 있습니다.
2분 전에 시작한 노래는
노래 길이가 3일 경우에 방금 시작한 노래가 되겠죠! 4 일 경우에는 나중에 시작할 노래가 될 것입니다.
이러한 정보들을 이용하면 방금 시작한 노래 i를 구할 수 있습니다!
i번 전의 노래가 j라고 하고, j번 노래의 길이가 len[j]라고 합시다.
또한, j번 노래 다음에 i번 노래가 나올 확률을 p[j][i]라고 한다면,
노래 i가 방금 시작할 확률은 노래 j가 끝난 다음에 노래 i가 틀어져야한다는 것과 같은 의미이죠!
따라서,
그러고 보니 처음에 저 (len[i]-1)분 전에 노래 j가 끝날 확률을 열 벡터 C에 넣어 놨었군요!
맨 위에 l-1분 전에 노래가 시작할 확률을 위에 먼저 놓았습니다. 그리고 방금 노래가 시작할 확률은 (l-1)*n 번째 행부터 시작하죠!
이를 이용하면 열 벡터 C를 이용해서 C(i)에서 C(i+1)로 가기 위한 가중치를 저장하는 행렬
즉, (l*n) x (l*n) 정사각행렬을 만들 수 있습니다!
이 행렬을 W라고 합시다.
그럼, W에서 위의 (l-1)*n 행 까지는 이전에 시작했던 노래들보다 1 분 늦게 시작한 노래들을 그대로 가져오면 됩니다!
1 2 3 4 | for (int i = 0, imax = (maxLen -1) * n; i < imax; i++) { W[i][i + n] = 1; } | cs |
그리고 (l-1)*n 행부터는 위에서 이용했던 공식을 이용하여서 만들 수 있죠!
합치면 아래와 같은 모습이 됩니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | void initW() { memset(W, 0, sizeof(W)); for (int i = 0, imax = (maxLen -1) * n; i < imax; i++) { W[i][i + n] = 1; } //슬라이딩 윈도우 행렬 for (int i = 0; i < n; i++) { for (int j = 0; j < n; j++) { W[(maxLen - 1)*n + i][(maxLen - len[j]) * n + j] = p[j][i]; } } } | cs |
이제 이렇게 W를 구하면, C(0)를 알고 있기 때문에 C(i)를 구하는 식을 만들 수 있죠!
또한, C(0)에서 0번 곡만 재생되고 있고, 이 0번 곡의 위치가 C[(l-1)*n]이기 때문에 행렬의 제곱을 계산하고 난 다음에 원하는 i번째 답을 뽑고 싶다면, W[i][(l-1)*n] 값만 보면 됩니다!
마지막으로... 만약 노래 j의 길이가 4라면, 방금 전에 시작한 j 노래 뿐만 아니라, 3분 전에 시작한 j 노래와 2분 전에 시작한 j 노래, 1분 전에 시작한 j 노래 모두 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 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 | #include<cstdio> #include<cstring> double p[50][50] = { 0 }; int len[50], num[10],n, maxLen; double res[50]; double W[200][200], tw[200][200]; //W : 슬라이딩 윈도우 double cw[200][200]; void initW() { memset(W, 0, sizeof(W)); for (int i = 0, imax = (maxLen -1) * n; i < imax; i++) { W[i][i + n] = 1; } //슬라이딩 윈도우 행렬 for (int i = 0; i < n; i++) { for (int j = 0; j < n; j++) { W[(maxLen - 1)*n + i][(maxLen - len[j]) * n + j] = p[j][i]; } } } void mul(double W1[200][200], double W2[200][200]) { double result[200][200] = { 0 }; for (int i = 0, imax = maxLen * n; i < imax; i++) { for (int j = 0, jm = maxLen * n; j < jm; j++) { result[i][j] = 0; for (int k = 0, km = maxLen * n; k < km; k++) { result[i][j] += W1[i][k] * W2[k][j]; } } } memcpy(W1, result, sizeof(result)); } void pow(int n) { if (n == 0) { return; } if (n == 1) { memcpy(tw, W, sizeof(W)); return; } pow(n / 2); memcpy(cw, W, sizeof(W)); mul(W, cw); if (n & 1) { mul(W, tw); } } int max(int i1, int i2) { return i1 > i2 ? i1 : i2; } int main() { int t; scanf("%d\n", &t); while (t--) { int k, m; maxLen = 0; scanf("%d %d %d\n", &n, &k, &m); for (int i = 0; i < n; i++) { scanf("%d \n", &len[i]); res[i] = 0; maxLen = max(maxLen, len[i]); } for (int i = 0; i < n; i++) { for (int j = 0; j < n; j++) { scanf("%lf \n", &p[i][j]); } } for (int i = 0; i < m; i++) { scanf("%d \n", &num[i]); } initW(); pow(k); for (int i = 0; i < m; i++) { int ri = num[i]; //노래 길이 len[num[i]] 이후에 시작한 i 노래들은 모두 영향을 줄 수 있는 노래들이다! for (int st = 0; st < len[num[i]]; st++) { res[i] += W[(maxLen - st - 1)*n + ri][(maxLen - 1)*n]; } } for (int i = 0; i < m; i++) printf("%.8lf ", res[i]); printf("\n"); } return 0; } | cs |
여담
완전히 정말로 어려웠습니다...
행렬로 계산하는건 처음 해봤네요!
대신에 정말 많은 것을 알아낸 것 같습니다!
뭐랄까.. 예전엔 못풀던 문제들도 이제는 풀 수 있을 것 같은 자신감을 얻었습니다.
저도 이 문제 결국 게속 생각해보다가 못풀어서 답지를 봤습니다 ㅎㅎ
이 문제는 노래의 길이가 l, 노래의 종류가 n, 시간이 k라고 한다면,
l*n차 정사각행렬이 됩니다. 따라서 시간복잡도는 가 됩니다!
'공부 > 알고리즘 문제풀이' 카테고리의 다른 글
[SW Expert Academy] 호둥이의 단어 찾기 (0) | 2018.07.20 |
---|---|
[SW Expert Academy] 코드 배틀!! 처음으로 해보는 1등! 허걱스 (0) | 2018.07.18 |
[알고스팟] 회전초밥 (2) | 2018.07.16 |
[알고스팟] 블록 게임 (0) | 2018.07.16 |
[알고스팟] 숫자 게임 (0) | 2018.07.16 |