백준 BOJ 24486번 : Counting Haybales
문제 난이도 : Diamond II
알고리즘 분류 : DP
N개의 자연수가 주어지고, 크기 차이가 1인 인접한 두 수를 서로 swap할 수 있다고 할 때, 만들 수 있는 서로 다른 수열의 개수를 구하는 문제이다.
문제의 풀이는 USACO 사이트를 참고하였다.
먼저 관찰을 통해 알 수 있는 사실은 홀짝성이 같은 수들끼리는 순서가 바뀔 수 없다는 것이다.
(이것은 오직 크기 차이가 1인 인접한 두 수만 교환이 가능하므로, 홀짝성이 같은 = 크기가 2 이상 차이나는 수는 서로 위치가 바뀔 수 없기 때문이다.)
원래 정석적인 풀이는 이 사실을 이용하여 그래프의 단방향 간선들을 만들 수 있으므로, 위상 정렬로 푸는 풀이로 보인다.
아무튼 홀짝성을 활용하여 문제를 풀이하기 위해, dp[i][j]를 수열의 앞 부분 i+j개의 수들 중 i개의 짝수와 j개의 홀수가 나타나는 길이 i+j인 서로 다른 수열의 개수라고 하자.
이제 점화식을 만들기 위해 다음의 값들을 구해보자.
olp[j'] : inversion 불가능한 i'번째 홀수와 j'번째 짝수(홀수가 왼쪽)가 있을 때, j'번째 짝수에 대해 가능한 odd's leftmost position
elp[j'] : inversion 불가능한 i'번째 짝수와 j'번째 홀수(짝수가 왼쪽)가 있을 때, j'번째 홀수에 대해 가능한 even's leftmost position
(inversion 불가능하다는 것은 두 수의 차가 1보다 큰 경우를 의미한다.)
N이 5,000 이하로 충분히 작으므로 O(N^2) 시간에 이중 for문으로 모든 쌍들에 대해 검사하여 olp와 elp 값들을 구할 수 있다.
olp[j'] : j'번째(= num[j]) 짝수 왼쪽에 있을 수 있는 홀수의 가장 왼쪽 번호 (max로 갱신하면서 왼쪽 임계값을 오른쪽으로 좁혀나감)
elp[j'] : j'번째(= num[j]) 홀수 왼쪽에 있을 수 있는 짝수의 가장 왼쪽 번호 (max로 갱신하면서 왼쪽 임계값을 오른쪽으로 좁혀나감)
이제 모든 이제 모든 i번째 짝수와 j번째 홀수에 대해 O(even x odd) 시간에 dp값을 계산해주면 된다. (even : 짝수 개수, odd : 홀수 개수)
j번째 홀수가 olp[i] = i번째 짝수 왼쪽에 있을 수 있는 홀수의 가장 왼쪽 번호와 같거나 오른쪽에 있는 경우, dp[i][j] += dp[i-1][j]가 성립한다.
반대로 i번째 짝수가 elp[j] = j번째 홀수 왼쪽에 있을 수 있는 짝수의 가장 왼쪽 번호와 같거나 오른쪽에 있는 경우에도, dp[i][j] += dp[i][j-1]이 성립한다.
최종적으로는 dp[even][odd]가 답이 된다. (even : 짝수 개수, odd : 홀수 개수)
#include <bits/stdc++.h>
#define int long long
using namespace std;
main() {
ios_base::sync_with_stdio(false);
cin.tie(NULL), cout.tie(NULL);
int T; cin >> T;
while(T--) {
int N; cin >> N;
vector<int> v(N), num(N);
int e = 0, o = 0;
for(int i=0; i<N; i++) {
cin >> v[i];
if(v[i] % 2 == 0) num[i] = ++e;
else if(v[i] % 2 == 1) num[i] = ++o;
}
vector<int> olp(e+1), elp(o+1);
for(int i=0; i<N; i++)
for(int j=i+1; j<N; j++)
if(v[i] % 2 != v[j] % 2 && abs(v[i] - v[j]) > 1) {
if(v[j] % 2 == 0) olp[num[j]] = max(olp[num[j]], num[i]);
if(v[j] % 2 == 1) elp[num[j]] = max(elp[num[j]], num[i]);
}
vector<vector<int>> dp(e+1, vector<int>(o+1));
dp[0][0] = 1;
int mod = 1e9 + 7;
for(int i=0; i<=e; i++)
for(int j=0; j<=o; j++) {
if(i > 0 && j >= olp[i]) dp[i][j] = (dp[i][j] + dp[i-1][j]) % mod;
if(j > 0 && i >= elp[j]) dp[i][j] = (dp[i][j] + dp[i][j-1]) % mod;
}
int ans = dp[e][o];
cout << ans << "\n";
}
}
여담으로 FFT를 활용하면 이 문제를 O(N log N) 시간에도 풀이가 가능하다고 한다.
'알고리즘 > 알고리즘 공부 내용 정리' 카테고리의 다른 글
11월 알고리즘 공부 : 최소 외접원/구, 가장 가까운 두 점, 번사이드 보조정리 등 (1) | 2022.11.22 |
---|---|
백준 BOJ 17030번 : Cow Dating 풀이 (Diamond V, 투 포인터) (0) | 2022.11.12 |
백준 BOJ 10058번 : 센서 네트워크 풀이 (Ruby V, 이분 매칭) (0) | 2022.10.29 |
이분 매칭 알고리즘 어려운 문제들 풀이 (Platinum II ~ Diamond) (0) | 2022.10.23 |
이분 매칭 최대 독립 집합 (Maximum Independent Set) 외 문제 풀이 (0) | 2022.10.20 |