아주 유명하고 어려운 문제입니다. 문제 목록 첫 페이지 최상단에 뜨는 문제라 순서대로 문제 푸는 초보자들을 좌절시키는 문제죠.
당연하게도 원형으로 된 배열이 주어지면 문제가 매우 어렵기 때문에, 건물이 $N\times2$짜리 직사각형인 경우를 먼저 풀어봅시다. 이렇게 되면 원타곤이 아니라 직타곤이라고 불러야 할까요…? 편의상 건물의 위쪽을 1행, 아래쪽을 2행이라 하고 왼쪽부터 차례로 0열부터 N-1열까지로 부르겠습니다.
먼저 동적 계획법을 사용하기 위해 세 배열을 다음과 같이 정의합니다.
- $a_i$: 건물의 1행과 2행 모두 $i-1$열까지 특수소대로 채우는 데 필요한 특수소대 수의 최솟값
- $b_i$: 1행은 $i$열까지, 2행은 $i-1$열까지 채우는 데 필요한 특수소대 수의 최솟값
- $c_i$: 1행은 $i-1$열까지, 2행은 $i$열까지 채우는 데 필요한 특수소대 수의 최솟값
그림으로 나타내면 아래와 같습니다. 회색이 특수소대로 채워진 구역, 흰색이 아직 채우지 않은 구역입니다.
이제 점화식을 구합시다. 1행과 2행 모두 $i$열까지 채우는 방법은 네 가지가 있습니다.
- 1행과 2행 모두 $i-1$열까지 채우고 1행 $i$열과 2행 $i$열에 특수소대 하나를 채움
- 1행과 2행 모두 $i-2$열까지 채우고 1행 $i-1$열과 1행 $i$열에 특수소대 하나, 2행 $i-1$열과 2행 $i$열에 하나를 채움
- 1행은 $i$열까지, 2행은 $i-1$열까지 채우고 2행 $i$열에 특수소대 하나를 채움
- 1행은 $i-1$열까지, 2행은 $i$열까지 채우고 1행 $i$열에 특수소대 하나를 채움
단, 첫 번째와 두 번째는 두 구역에 걸쳐 특수소대 하나를 배치하므로 두 구역에 있는 적의 수가 합쳐서 $W$보다 작은 경우에만 가능합니다. 정리하자면,
여기서 $e_{i,j}$는 $i$행 $j$열에 있는 적의 수입니다. 한편 1행은 $i+1$열까지 채우고 2행은 $i$열까지 채우는 방법은 두 가지가 있습니다.
- 1행과 2행 모두 $i$열까지 채우고 1행 $i+1$열에 특수소대 하나를 채움
- 1행은 $i-1$열, 2행은 $i$열까지 채우고 1행 $i$열과 1행 $i+1$열에 특수소대 하나를 채움
따라서
마지막으로 1행은 $i$열까지, 2행은 $i+1$열까지 채우는 방법을 위와 비슷하게 계산하면 $c_{i+1}$에 대한 점화식을 구할 수 있습니다.
점화식을 구한 걸로 끝이 아니라 초기값도 구해야죠. $a_0$는 1행과 2행 모두 -1열까지 채우라는 의미인데 채울 필요가 없으니 0입니다. $b_0$와 $c_0$는 둘 다 1이고요. 최종 정답은 $a_N$입니다.
이제 다시 원형으로 돌아옵시다. 앞에서 푼 선형 문제는 1번 구역과 $N$번 구역에 걸쳐 특수소대를 채우지 않고 $N+1$번 구역과 $2N$번 구역에 걸쳐 특수소대를 채우지 않는 경우와 동일합니다. 만약 1번 구역과 $N$번 구역에 걸쳐 특수소대를 채우고 $N+1$번 구역과 $2N$번 구역에 걸쳐 특수소대를 채우지 않으면 초기 조건이 달라져야 합니다. 이때는 $N\times2$ 직사각형 건물에서 1행 0열과 1행 $N-1$행이 없어졌다고 생각하면 됩니다. 그럼 $a_1$은 1, $b_1$은 2, $c_1$은 $e_{2,0}+e_{2,1}\le W$이면 1, 아니면 2가 초기 조건이 되고, 최종 정답은 $c_{N-1}+1$입니다. 마지막에 1을 더하는 이유는 1행 0열과 1행 $N-1$행에 배치된 특수소대가 있기 때문이죠.
1번과 $N$번 구역에 걸쳐 특수소대를 채우지 않고 $N+1$번과 $2N$번 구역에 걸쳐 특수소대를 채우면 위와 정확히 반대 상황이며, 양쪽 다 걸쳐 채운다고 하면 $a_1$은 0, $b_1$과 $c_1$은 1이고 최종 정답은 $a_{N-1}+2$입니다.
마지막으로, $N$이 1일 때 예외 처리가 필요합니다.
#include<bits/stdc++.h>
using namespace std;
int n, w, res;
int e[10000][2], a[10001], b[10000], c[10000];
void solve(int start) {
for (int i = start; i < n; i++) {
a[i+1] = min(b[i] + 1, c[i] + 1);
if (e[i][0] + e[i][1] <= w)
a[i+1] = min(a[i+1], a[i] + 1);
if (i > 0 && e[i-1][0] + e[i][0] <= w && e[i-1][1] + e[i][1] <= w)
a[i+1] = min(a[i+1], a[i-1] + 2);
if (i < n-1) {
b[i+1] = a[i+1] + 1;
if (e[i][0] + e[i+1][0] <= w)
b[i+1] = min(b[i+1], c[i] + 1);
c[i+1] = a[i+1] + 1;
if (e[i][1] + e[i+1][1] <= w)
c[i+1] = min(c[i+1], b[i] + 1);
}
}
}
int main(void) {
ios_base::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
int t;
cin >> t;
while (t--) {
cin >> n >> w;
res = 30000;
for (int i = 0; i < n; i++)
cin >> e[i][0];
for (int i = 0; i < n; i++)
cin >> e[i][1];
a[0] = 0;
b[0] = c[0] = 1;
solve(0);
res = min(res, a[n]);
if (n > 1 && e[0][0] + e[n-1][0] <= w) {
a[1] = 1;
b[1] = 2;
c[1] = e[0][1] + e[1][1] <= w? 1 : 2;
solve(1);
res = min(res, c[n-1] + 1);
}
if (n > 1 && e[0][1] + e[n-1][1] <= w) {
a[1] = 1;
b[1] = e[0][0] + e[1][0] <= w? 1 : 2;
c[1] = 2;
solve(1);
res = min(res, b[n-1] + 1);
}
if (n > 1 && e[0][0] + e[n-1][0] <= w && e[0][1] + e[n-1][1] <= w) {
a[1] = 0;
b[1] = c[1] = 1;
solve(1);
res = min(res, a[n-1] + 2);
}
cout << res << '\n';
}
return 0;
}