Processing math: 100%
알고리즘 블로그
article thumbnail
Published 2022. 9. 12. 13:07
[BOJ 1328] 고층 빌딩 ★ PS 문제들

문제 링크

질문은 언제나 환영입니다.

3가지 풀이법이 나오네요. 글에는 없는 자신만의 풀이법이 존재하신다면 알려주세요!

두 가지 풀이법은 O(n3)짜리 풀이이고, 한 가지 풀이법은 O(n2)짜리입니다.

1. 풀이

더보기

문제에서 구해야 하는 것은 조건을 만족하는 배치의 수입니다.

세세한 배치의 성질들에 대해 알아볼 방법은 떠오르지 않고, 작은 케이스부터 풀다 보면 패턴이 보이기 때문에 동적 계획법을 시도해볼 수 있습니다.

 

1.1. 3차원 동적 계획법

B(x,y,z):=x개의 빌딩 중 왼쪽에서 y개, 오른쪽에서 z개만 보이는 경우의 수

위와 같이 정의하는 것은 아주 자연스럽습니다.

 

점화식을 도출할 때는 B(x1,)와의 관계를 우선 생각 해봅시다.

새로 생긴 가장 긴 막대기를 어디다 꽂아 넣을지를 고민할 수도 있고, 가장 짧은 막대기를 어디다 꽂아 넣을지를 고민할 수도 있습니다.

이 상황에서는 직접 해 보면 후자가 먹힌다는 것을 알 수 있습니다.

 

가장 짧은 막대기는 양 끝이 아니라면 다른 막대기에 가려 yz에 영향을 미치지 않습니다.

따라서 B(x,y,z)=B(x1,y,z)+가 되겠군요.

 

남은 경우는 양 끝에 가장 짧은 막대기를 배치하는 것인데, 각 경우에 y가 하나 증가하고 z가 하나 증가하므로 아래와 같은 점화식을 도출할 수 있습니다.

B(x,y,z)=B(x1,y,z)(x2)+B(x1,y1,z)+B(x1,y,z1)

 

이 점화식을 구현하면 단순하게 O(n3)B(n,l,r)을 계산하여 문제를 풀 수 있습니다.

 

1.2. 2차원 동적 계획법

문제에서 주어진 요소를 모두 활용하는 3차원 동적 계획법을 풀긴 했지만, 다소 복잡할 것이라 생각했다면 다른 방식을 시도해 볼 수도 있습니다.

 

문제에서 주어진 상황에서 가장 긴 빌딩을 기준으로 양쪽이 완벽하게 나뉜다는 것을 알 수 있습니다. 따라서 가장 긴 빌딩을 어디로 할 것인지만 정하면 양쪽 각각에서 상대적인 순서 관계를 정해준 뒤, 빌딩들을 배분하면 됩니다.

 

B(x,y):=x개의 빌딩 중 y개만 보이는 경우의 수

라고 정의한다면, 답은 nr+1p=l(n1p1)B(p1,l1)B(np,r1)이 된다는 것을 알 수 있습니다.

 

그럼 B(x,y)를 계산하는 방법만 찾으면 됩니다.

가장 긴 빌딩을 어디다가 꽂아 넣을지 정하면 그 뒷쪽에 위치하는 빌딩들의 순서는 중요하지 않고, 가장 긴 빌딩을 기준으로 양쪽이 독립적이라는 점을 이용해 앞선 논리와 같이 다음과 같은 점화식을 세울 수 있습니다.

B(x,y)=xi=yB(i1,y1)(x1)!(i1)!

 

이 점화식을 통해 계산하면 O(n3)만에 문제를 풀 수 있습니다.

 

1.3. 점화식이 간단한 2차원 동적 계획법

B(x,y)=xi=yB(x1,y1)(x1)!(i1)!은 혼자 O(n)을 잡아먹는 다소 복잡한 점화식입니다.

 

만약 가장 긴 빌딩의 위치를 정하는 것이 아니라 가장 작은 빌딩의 위치를 정하는 것이었다면 3차원 동적 계획법에서 점화식을 세웠던 과정과 동일한 논리로 다음의 점화식을 세울 수 있습니다.

B(x,y)=B(x1,y1)+(x1)B(x1,y)

 

따라서 O(n2)에 문제를 풀 수 있습니다.

2. 코드

더보기

3차원 동적 계획법과 점화식이 간단한 2차원 동적 계획법은 그냥 몇 줄 만에 구현이 가능합니다.

따라서 구현이 상대적으로 복잡한 2차원 동적 계획법의 코드만 소개합니다.

<cpp />
#include <bits/stdc++.h> using namespace std; using ii = pair<int,int>; using ll = long long; #define rep(i,a,b) for (auto i = (a); i <= (b); ++i) #define all(x) (x).begin(), (x).end() #define siz(x) int((x).size()) #define Mup(x,y) x = max(x,y) #define mup(x,y) x = min(x,y) const int N = 103, MOD = 1e9+7; int n, l, r; ll dp[N][N]; bool ready[N][N]; #define Add(x,y) x = add(x,y) #define Mul(x,y) x = mul(x,y) ll add(ll x, ll y) { return (x=(x+y)%MOD) >= 0 ? x : x+MOD; } ll mul(ll x, ll y) { return (x=(x*y)%MOD) >= 0 ? x : x+MOD; } ll pw(ll x, ll e) { ll r = 1; while (e > 0) { if (e%2) r = mul(r,x); e /= 2, x = mul(x,x); } return r; } ll inv(ll x) { return pw(x,MOD-2); } const int F = N; ll fac[F+1] {1}, caf[F+1]; void precalc() { int i = 1; for (; i <= F; ++i) fac[i] = mul(fac[i-1],i); caf[F] = inv(fac[F]); for (i = F; i; --i) caf[i-1] = mul(i,caf[i]); } ll C(int n, int k) { if (k < 0 or n < k) return 0; return mul(mul(fac[n],caf[k]),caf[n-k]); } ll calc(int x, int y) { // dp[x][y] := x개 중 y개만 보임 if (x == 0 and y == 0) return 1; if (x == 0 or y == 0) return 0; if (ready[x][y]) return dp[x][y]; ready[x][y] = true; rep(i,y,x) { Add(dp[x][y],mul(calc(i-1,y-1),mul(fac[x-1],caf[i-1]))); } return dp[x][y]; } int main() { precalc(); scanf("%d %d %d", &n, &l, &r); ll ans = 0; rep(piv,l,n-r+1) { Add(ans, mul(mul(calc(piv-1,l-1),calc(n-piv,r-1)),C(n-1,piv-1))); } printf("%lld", ans); }
profile

알고리즘 블로그

@도훈.

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!