집합이 존재할 때 각각의 부분집합에 대해 그 집합의 부분집합의 합(Sum Over Subsets, SOS)을 빠르게 계산하는 방법에 대해서 다룬다. 이 방법은 DP 테이블을 채우면서 부분집합의 합을 계산하기 때문에 SOS DP라고 불린다. 원소의 개수가 n인 집합의 부분집합의 개수가 2n이고, SOS DP의 기본 시간복잡도가 Θ(2n⋅n)이기 때문에 일반적으로 n은 20 이하이다.
SOS DP는 집합을 비트마스크로 표현하며, 따라서 비트필드를 이용하게 된다. 집합을 0-based로 나타내면 0≤p<2n을 만족하는 정수 p는 p의 켜진 비트에 해당하는 원소들을 포함하는 집합을 나타내는 것으로 생각할 수 있다. 예를 들어 0번째, 3번째 원소를 포함하는 집합은 20+23=9라는 정수로 표현하게 되고 1번째, 3번째, 6번째 원소를 포함하는 집합은 21+23+26=74라는 정수로 표현하게 된다. 이 방법으로 집합을 표현할 경우 두 집합의 포함 관계는 다음과 같이 확인할 수 있다.
- 두 집합 A,B를 나타내는 정수가 각각 x,y일 때 (x&y)=y인 경우 B⊂A이고 (y&x)=x인 경우 A⊂B이다.
이 사실을 바탕으로 다음과 같이 DP 식을 정의한다. Sp는 정수 p로 표현되는 집합을 의미한다.
a[p][0]=|Sp|
a[p][k]=a[p][k−1]+{0(p&2k−1=0)a[p−2k−1][k−1](p&2k−1=2k−1)(0<k<n)
a[p][k]는 집합 Sp의 각각의 부분집합을 표현하는 수들 중 k번째 이상의 비트가 p의 k번째 이상의 비트와 전부 일치하는 집합의 원소들의 합을 의미한다.
이 방법으로 비트필드를 채우면 a[p][n]에 p의 부분집합의 합이 저장된다. 다음과 같이 구현할 수 있다.
void sos(int n)
{
for(int i = 0; i < 1 << n; i++)
{
for(int j = 1; j <= n; j++)
{
a[i][j] = a[i][j - 1];
if(i & 1 << j - 1)a[i][j] += a[i - (1 << j - 1)][j - 1];
}
}
}
[참고 링크]
[연습문제]
BOJ 18719. Binomial (Diamond V)
뤼카 정리에 의하면 (nk)가 홀수인 경우는 (n&k)=k인 경우와 같다. 따라서 입력되는 각각의 값의 개수를 a[p][0]에 저장한 다음 SOS DP를 이용해서 문제를 해결할 수 있다. 구하는 답은 ∑220−1i=0a[i][0]⋅a[i][20]이 된다.
'Algorithm > E. Dynamic Programming' 카테고리의 다른 글
80. Tree DP (0) | 2021.07.21 |
---|---|
79. Bit DP (0) | 2021.07.13 |
78. Longest Common Subsequence (2) | 2021.07.07 |
77. Longest Increasing Subsequence (0) | 2021.07.02 |
76. Prefix Sum (1) | 2021.07.01 |