문제 : https://www.acmicpc.net/problem/1006
[ 코멘트 ]
처음에는 DP 말고 Set-cover Problem의 관점에서 습격자 초라기를 바라봤다.
백준 예제 입력 1번을 가지고 다뤄보면,
8 100
70(1) 60(2) 55(3) 43(4) 57(5) 60(6) 44(7) 50(8)
58(9) 40(10) 47(11) 90(12) 45(13) 52(14) 80(15) 40(16)
총 16개의 구역이 존재하고, 한 소대가 최대 100명을 처리할 수 있다.
이때, 한 번에 두 구역을 커버 가능한 경우를 모두 뽑아보면 다음과 같다.
{ 2, 10 }, { 3, 4 }, { 4, 5 }, { 7, 8 }, { 8, 16 }, { 9, 10 }, { 10, 11 }, { 13, 14 }, { 9, 16 } 이고
여기에서 선택받지 못한 { 1 }, { 6 }, { 12 }, { 15 } 는 무조건 하나의 특수 소대가 커버해야 한다.
따라서 이 문제를 집합 U = { 2, 3, 4, 5, 7, 8, 9, 10, 11, 13, 14, 16 } 를
S = { { 2, 10 }, { 3, 4 }, { 4, 5 }, { 7, 8 }, { 8, 16 }, { 9, 10 }, { 10, 11 }, { 13, 14 }, { 9, 16 } } 에서
최소 개수의 원소들을 뽑아서 집합 U를 만드는 문제로 바라보려 했다.
하지만, 이런 Set-cover Problem은 NP hard Problem으로 항상 최적의 해를 구하는 알고리즘이 없어서,,, 포기했다,,,,
그 후, DP 배열을 아무리 생각해도 아이디어가 떠오르지 않아 DP 배열 정의만 포스팅을 참고해 문제를 풀고 다른 분들 코드랑 비교해보는 시간을 가졌다...! ( 언제쯤 혼자서 이 정도 문제를 풀 수 있을까... )
[ 알고리즘풀이 ]
정의 )
Enemy[2][N] := Enemy[0][N] 은 바깥쪽 구역, Enemy[1][N] 은 안쪽 구역의 적의 수입니다.
DP[0][N] := 바깥쪽구역 1 ~ N, 안쪽구역 1 ~ N - 1 을 커버하는데 필요한 최소 특수 부대의 수.
DP[1][N] := 바깥쪽구역 1 ~ N - 1, 안쪽구역 1 ~ N 을 커버하는데 필요한 최소 특수 부대의 수.
DP[2][N] := 바깥쪽구역 1 ~ N, 안쪽구역 1 ~ N 을 커버하는데 필요한 최소 특수 부대의 수.
점화식 )
DP[0][N] = min(dp[1][N - 1] + outer, dp[2][N - 1] + 1, );
DP[1][N] = min(dp[0][N - 1] + inner, dp[2][N - 1] + 1, );
DP[2][N] = min({ dp[0][N] + 1, dp[1][N] + 1, dp[2][N - 1] + horizon, , dp[2][N - 2] + outer + inner });
그림으로 위의 점화식을 자세히 이해해보겠습니다.
1) DP[0][N]
DP[0][5] 는 아래와 같은 2가지 경우 중에 하나로 값을 찾을 수 있습니다.
DP[1][4] 에서 Out4랑 Out5를 커버하는데 필요한 특수 소대의 수(Outer) 를 더한 값이랑,
DP[2][4] 에서 Out5를 커버하는 특수 소대의 수(1) 을 더한 값 중 작은 값이 됩니다.
즉, 다음과 같은 점화식을 얻을 수 있습니다.
DP[0][N] = min(dp[1][N - 1] + outer, dp[2][N - 1] + 1, );
2) DP[1][N]
DP[1][5] 은 아래와 같은 2가지 경우 중에 하나로 값을 찾을 수 있습니다.
즉, 다음과 같은 점화식을 얻을 수 있습니다.
DP[1][N] = min(dp[0][N - 1] + inner, dp[2][N - 1] + 1, );
3) DP[2][N]
DP[2][5] 는 아래와 같은 4가지 경우 중에 하나로 답을 찾을 수 있습니다.
위에서 DP[0][5], DP[1][5] 의 값을 먼저 구했으므로, 그 값들을 이용해서 DP[2][5] 를 채울 수 있습니다.
따라서, 다음과 같은 점화식이 성립합니다.
DP[2][N] = min({ dp[0][N] + 1, dp[1][N] + 1, dp[2][N - 1] + horizon, , dp[2][N - 2] + outer + inner });
하지만, 이렇게 DP 배열을 채워나가면 원형으로 이어져있다는 점을 고려하지 못하기 때문에, Out[1] 과 Out[N]을 동시에 커버할 수 있는 경우, In[1]과 In[N]을 동시에 커버할 수 있는 경우, Out[1] 과 Out[N] / In[1]과 In[N]을 동시에 커버할 수 있는 경우에 대해서도 초기값을 다르게 해서 DP 배열을 채우고 총 4가지 경우에 대해서 가장 작은 값을 답으로 출력하면 됩니다.
#include<iostream>
#include<algorithm>
#include<cstring>
#define INF 987654321
using namespace std;
int dp[3][10001], N, W, enemy[2][10001];
void solve(void) { // 그냥 점화식대로 DP배열을 채우는 함수.
for (int i = 2; i <= N; i++) {
int outer = 2 - (enemy[0][i] + enemy[0][i - 1] <= W);
int inner = 2 - (enemy[1][i] + enemy[1][i - 1] <= W);
int horizon = 2 - (enemy[0][i] + enemy[1][i] <= W);
dp[0][i] = min(dp[2][i - 1] + 1, dp[1][i - 1] + outer);
dp[1][i] = min(dp[2][i - 1] + 1, dp[0][i - 1] + inner);
dp[2][i] = min({ dp[2][i - 1] + horizon, dp[0][i] + 1, dp[1][i] + 1, dp[2][i - 2] + outer + inner });
}
return;
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0); cout.tie(0);
int T;
cin >> T;
while (T--) {
cin >> N >> W;
for (int i = 0; i < 2; i++)
for (int j = 1; j <= N; j++)
cin >> enemy[i][j];
if (N == 1) {
cout << 2 - (enemy[0][1] + enemy[1][1] <= W) << '\n';
continue;
}
int ans = INF;
memset(dp, 0, sizeof(dp));
dp[0][1] = dp[1][1] = 1;
dp[2][1] = 2 - (enemy[0][1] + enemy[1][1] <= W);
solve();
ans = min(ans, dp[2][N]);
if (enemy[0][N] + enemy[0][1] <= W) {
dp[2][0] = INF; dp[0][1] = 1; dp[1][1] = INF; dp[2][1] = 2;
solve();
ans = min(ans, dp[1][N]);
}
if (enemy[1][N] + enemy[1][1] <= W) {
dp[2][0] = INF; dp[1][1] = 1; dp[0][1] = INF; dp[2][1] = 2;
solve();
ans = min(ans, dp[0][N]);
}
if (enemy[0][N] + enemy[0][1] <= W && enemy[1][N] + enemy[1][1] <= W) {
dp[2][0] = INF; dp[0][1] = dp[1][1] = INF; dp[2][1] = 2;
solve();
ans = min(ans, dp[2][N - 1]);
}
cout << ans << '\n';
}
}
'Problem Solving > BOJ' 카테고리의 다른 글
[BOJ] 1167 : 트리의 지름 - travelbeeee (0) | 2020.03.20 |
---|---|
[BOJ] 1068 : 트리 - travelbeeee (0) | 2020.03.20 |
[BOJ] 1126 : 같은 탑 - travelbeeee (0) | 2020.03.18 |
[BOJ] 16434 : 드래곤 앤 던전 - travelbeeee (0) | 2020.03.17 |
[BOJ] 15665 : N과 M(11) - travelbeeee (0) | 2020.03.17 |