잘 생각해보면 1을 넣을 수 있는 개수가 제한되어 있고, 자리수를 늘려감에 따라 추가적으로 1을 넣을 수 있는지 여부는 뒤에 1이 몇 개 존재하는지에 지배당하는 구조로 이루어져 있다. 따라서 하위 계층의 정보를 활용하여 상위 계층을 구성할 수 있는 상황이므로 DP로 풀 수 있음을 파악할 수 있다.
이때, 자리수를 늘려감에 따라 1을 몇개 썼는지 여부도 고려해야하므로 parameter를 1개 추가해주면 된다.
Let $dp[i][j]$ : number of sequence with length i that can be made using 1 j times. $ dp[i][j] = dp[i - 1][j] + dp[i - 1][j - 1]$
만약 어떠한 자리까지 고려해서 숫자를 만드는 상황에서, 최대한의 1을 활용해서 만들 수 있는 개수가 주어진 k보다 더 큰 경우에는 그 다음 자리수에 1이 무조건 들어갈 수 밖에 없다는 의미로 이해할 수 있다. 해당하는 정보를 활용해서 k번째 원소를 구해줄 수 있다. 또한 1을 사용한 상황이므로 그보다 작은 자리수부터는 사용할 수 있는 1의 개수를 1개씩 줄여나가면서 반복해주면 된다.
#include <bits/stdc++.h>
#define fastio ios_base::sync_with_stdio(0), cin.tie(0), cout.tie(0)
using namespace std;
typedef long long ll;
ll dp[32][32]; // dp[i][j] = i번째 자리까지 고려할 때 총 소모된 1의 개수가 j인 개수
int main(void){
memset(dp, 0, sizeof(dp));
ll n, l, i;
cin >> n >> l >> i;
dp[0][0] = 1;
dp[1][0] = 1;
dp[1][1] = 1;
for(int i = 2; i <= n; i++){
dp[i][0] = 1;
for(int j = 1; j <= i; j++){
dp[i][j] = dp[i - 1][j] + dp[i - 1][j - 1];
}
}
while(n > 0){
ll check = 0;
for(int i = 0; i <= l; i++){
check += dp[n - 1][i];
}
if(check >= i) cout << 0;
else{
i -= check;
l--;
cout << 1;
}
n--;
}
cout << "\n";
return 0;
}
해당 코드를 보고 대략적으로 이해하도록 하자. 맨 마지막 while문을 보면 알겠지만, 해당 index 이전 dp값에 대한 정보를 가지고 index의 값이 0인지 1인지를 추정한다는 점을 주의하도록 하자.
Tree DP
일반적으로 해당 트리에 대한 서브 트리에 대한 정보를 가지고 계속해서 상위 트리로 이동하면서 문제를 처리하는 방식을 의미한다.