문제 링크 : https://www.acmicpc.net/problem/1722
문제
1부터 N까지의 수를 임의로 배열한 순열은 총 N! = N×(N-1)×…×2×1 가지가 있다.
임의의 순열은 정렬을 할 수 있다. 예를 들어 N=3인 경우 {1, 2, 3}, {1, 3, 2}, {2, 1, 3}, {2, 3, 1}, {3, 1, 2}, {3, 2, 1}의 순서로 생각할 수 있다. 첫 번째 수가 작은 것이 순서상에서 앞서며, 첫 번째 수가 같으면 두 번째 수가 작은 것이, 두 번째 수도 같으면 세 번째 수가 작은 것이….
N이 주어지면, 아래의 두 소문제 중에 하나를 풀어야 한다. k가 주어지면 k번째 순열을 구하고, 임의의 순열이 주어지면 이 순열이 몇 번째 순열인지를 출력하는 프로그램을 작성하시오.
입력
첫째 줄에 N(1 ≤ N ≤ 20)이 주어진다. 둘째 줄의 첫 번째 수는 소문제 번호이다. 1인 경우 k(1 ≤ k ≤ N!)를 입력받고, 2인 경우 임의의 순열을 나타내는 N개의 수를 입력받는다. N개의 수에는 1부터 N까지의 정수가 한 번씩만 나타난다.
출력
k번째 수열을 나타내는 N개의 수를 출력하거나, 몇 번째 수열인지를 출력하면 된다.
접근 방법
주어진 시간은 2초밖에 되지 않기 때문에 노가다로 하면 시간 초과가 뜰 것이다.
먼저 소문제 번호 = 1인 경우를 보자.
N = 5
배열 = {1, 2, 3, 4, 5}
k = 61이라고 해보자.
61번째 순열을 어떻게 구할 수 있을까?
먼저 61번째의 첫번째 숫자는 무엇일지 생각해보자. 2*4! < 61 < 3*4! 이므로 첫번째 숫자는 3이다.
1 _ _ _ _ => 4! (24) 가지가 있음
2 _ _ _ _ => 4! (24) 가지가 있음
3 _ _ _ _ => 4! (24) 가지가 있음
이런 방식을 활용해보면 처음 61에서 24씩 빼면서 경계를 찾아 나가다가
61 - 24 = 37
37 - 24 = 13
13 - 24 < 0 가 됨. 24를 세번째 뺀 것이므로 세번째 범위에 답이 있고 처음 숫자는 3이 되는 것이다.
그 다음을 보자. 우선 3은 가장 앞자리로 고정이 되었기 때문에 제외하고 1, 2, 4, 5 가 남았다.
3, 1, _ _ _ => 3! (6) 가지가 있음 (49 ~ 54)
3, 2, _ _ _ => 3! (6) 가지가 있음 (55 ~ 60)
3, 4, _ _ _ => 3! (6) 가지가 있음 (61 ~ 66)- > 이 중에 61번째 순열이 있음
3, 5, _ _ _ => 3! (6) 가지가 있음 (67 ~ 73)
단순하게 보면 위와 같은데, 처음에 썼던 빼기 규칙을 써보면
13 - 6 = 7 > 0이고
7 - 6 = 1 > 0이고
1 - 6 <0 이므로 세번째 범위에 답이 있고 두번째 숫자는 4가 되는 것이다.
이런 식으로 계속 빼 나가면서 숫자를 지정해나가면 된다.
다음 소문제 번호 = 2인 경우를 보자.
위에서 했던 방식을 거꾸로 하면 된다.
N = 5일 때 61번째 순열인 {3, 4 ,1 ,2, 5} 를 보자. {3, 4, 1, 2, 5}를 보고 어떻게 61번째 순열인지 알 수 있을까?
각 자리의 숫자보다 작은 수로 시작하는 순열의 가지수를 세가면서 더하면 된다.
1, _ _ _ _ 는 4! (24)가지
2, _ _ _ _ 도 4!(24)가지이다.
그럼 우선 sum은 24+24 = 48이 된다.
3은 visit 표시를 해 놓고 다음 차례인 4를 보자.
3, 4, _ _ _ <- 이 순열의 전에는
3, 1, _ _ _
3, 2, _ _ _
이 두 순열이 우선이 됐을 것이다. 각각 3! (6) 가지이므로 48 + 6 + 6 = 60이 된다.
3, 4, _ _ _ 정해졌고 남은 수는 1, 2, 5 인데 구하는게 3, 4, 1, 2, 5가 몇번째인지 구하는 것이므로
sum 인 60에 1을 더하면 된다.
import java.util.*;
public class BOJ_1722_순열의순서 {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
long[] fac = new long[21];
fac[0] = 1;
for(int i = 1; i<=20 ;i++) fac[i] = i*fac[i-1];
int N = sc.nextInt();
int num = sc.nextInt();
long k = 0;
int[] arr = new int[N+1];
boolean[] visited = new boolean[N+1];
Arrays.fill(arr,0);
Arrays.fill(visited,false);
Vector<Integer> ansVec = new Vector<Integer>();
long ans = 1;
if (num==1){
k = sc.nextLong(); //Long으로 입력하지 않으면 런타임 에러뜸
for(int i = 0; i<N; i++){
for(int j = 1; j<=N; j++){
if(visited[j]) continue; //이미 사용된 숫자는 패스
if(k - fac[N-1-i] > 0) { //0이랑 같으면 1 3 4 2가 오답으로 출력됨
k -= fac[N-1-i];
}
else {
ansVec.add(j);
visited[j] = true;
break;
}
}
}
for(int i = 0; i<ansVec.size(); i++){
System.out.print(ansVec.elementAt(i) + " ");
}
}
else if (num==2){
for(int i = 0; i<N; i++){
arr[i] = sc.nextInt();
}
for(int i = 0; i<N; i++){
for(int j = 1; j<arr[i]; j++){ //arr[i] 보다는 작은 수가 앞에 나올 수 있으므로
if(visited[j] == false){
ans += fac[N-1-i];
}
}
visited[arr[i]] = true;
}
System.out.println(ans);
}
}
}
'Algorithm(BOJ) > Mathematics' 카테고리의 다른 글
[Java] 백준 1339번 - 단어 수학 (수학, 시뮬레이션) (2) | 2023.12.05 |
---|---|
[Java] 백준 1669번 - 멍멍이 쓰다듬기 (1) | 2023.03.12 |
[C++] 백준 10989번 - 수 정렬하기 3 (메모리 초과 주의) (0) | 2021.06.06 |
[C++] 백준 4375번 - 1 (시간초과, Modular 연산 활용) (0) | 2021.04.28 |
[C++] 백준 1929 - 소수 구하기 (에라토스테네스의 체) (0) | 2021.01.28 |