순열의 순서
문제 해석
- N!가지의 순열 중 K번째 순열 또는 특정 순열이 몇번째 순열인지 맞추는 문제입니다.
설계(20)
- 생각보다 까다로웠던 문제였습니다.
- 우선, factorial값(<=20)을 구해놓습니다.
- 주어지는 입력이 1번인 경우 K번째 순열을 찾아야 합니다.
- 재귀함수를 이용하여 K보다 큰 인덱스가 나올때까지 factorial(N-1)을 더해줍니다.
- 예를 들어, 4!기준 15번째 순열을 찾고자 할 때 “1 x x x”인 경우의 수는 3!= 6가지입니다.
- 그러면, 15번째 순열보다 작기 때문에 맨 앞에 1이 들어갈 수 없습니다.
- 계속 진행했을 때 “1 x x x” ~ “3 x x x”인 경우는 총 18가지로 맨 앞에 3이 나오지 않고 바로 아래의 2가 들어가는 것을 확인 할 수 있습니다.
- 이런식으로 재귀를 이용하여 모든 x가 채워질 때까지 반복하면 원하는 순열을 얻을 수 있습니다.
- 반대로, 순열이 주어지고 몇번 째 순열인지 맞추는 문제의 경우에는 1~N을 탐색하면서 아직 사용하지 않은 숫자가 있는 경우 원하는 숫자가 나올때까지 (N-1)!씩 더해줍니다.
- 이를 재귀함수로 반복하면 몇번째 순열인지 알 수 있습니다.
구현(40)
코드
#include <iostream>
#include <vector>
using namespace std;
#define MAXN 21
typedef unsigned long long ll;
ll num[MAXN];
bool used[MAXN];
int arr[MAXN];
int N,idx;
ll t_idx;
bool flag;
int ans_arr[MAXN];
ll ans;
void Find_Idx(int len, ll cnt){
if (flag) return;
if (len == N){
ans = cnt+1;
flag = true;
return ;
}
ll sum = 0;
for (int i = 1; i<= N; i++){
if (used[i]) continue;
if (arr[len] == i){
used[i] = true;
Find_Idx(len + 1, cnt + sum);
}
sum += num[N-len-1];
}
}
void Find_Perm(int len, ll cnt){
if (flag) return;
if (len == N){
flag = true;
return;
}
ll sum = cnt;
for (int i = 1; i <=N; i++){
if (used[i]) continue;
if (sum + num[N-len-1] > t_idx){
ans_arr[len] = i;
used[i] = true;
Find_Perm(len+1,sum);
}
sum += num[N-len-1];
}
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
num[0] = 1;
for (int i =1; i < MAXN; i++){
num[i] = num[i-1]*i;
}
cin >> N >> idx;
if (idx == 2){
for (int i = 0; i<N; i++){
cin >> arr[i];
}
Find_Idx(0,0);
cout << ans <<"\n";
}
else{
cin >> t_idx;
Find_Perm(0,1);
for (int i = 0; i <N; i++){
cout << ans_arr[i] <<" ";
}
}
return 0;
}
디버깅
- 1번의 경우에 파라미터를 넣을 때, {0,0}을 넣는 실수를 하였습니다. 4!기준 맨 처음 1 2 3 4는 인덱스가 1이기 때문입니다.
- 또한, 순열의 인덱스 t_idx를 int형으로 선언하는 실수를 하였습니다. 최대 20!에서 인덱스를 찾기 위해서는 long long형을 선언해야합니다.
제출결과
- 0 ms
마무리
- 재귀함수를 이용한 수학문제였습니다. solved.ac 기준 실버치고는 꽤 애를 먹었던 문제였습니다.