문제 링크 : 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개의 수를 출력하거나, 몇 번째 수열인지를 출력하면 된다.
풀이
순열 문제로 둔갑한 수학 문제이다.
순열을 전부다 구하기 위해선 O(N!) 의 시간이 걸리는데, 위 문제의 입력값은 N의 범위가 20이기때문에 순열을 전부다 구해서 문제를 풀면 시간 초과가 나게된다. (보통 순열 문제에서 N의 범위가 10을 좀만 초과해도 시간 초과가 난다.)
따라서 문제의 규칙을 최대한 이용해야하는데, 이 문제는 두 개로 쪼갤 수 있는데 두 문제는 거의 비슷하다.
1. k가 주어지면 k번째 순열 구하기
2. 임의의 순열이 주어지면 몇 번째 순열인지 출력하기
우선 1번 부터 살펴보자면,
k번째 순열을 구하는데에 있어서, 가장 중요한 것은 순열의 규칙성이다.
만약 n = 10 이라고 가정하면, 순열을 처음부터 나열하면
1 2 3 4 5 6 7 8 9 10
1 2 3 4 5 6 7 8 10 9
...
2 1 3 4 5 6 7 8 9 10
...
3 1 2 4 5 6 7 8 9 10
...
이 될 것이다.
맨 앞 숫자가 바뀌지 않고, (n-1) 개의 숫자가 순열과정을 거친 후에야 맨 앞 숫자가 올라가는데,
이 과정은 (n-1)! 번이고, 총 10번 거치게된다. 따라서 몇 번째인지 알면, 부등호를 이용해 순열의 첫 번째 숫자를 알 수 있게 된다. 그럼 나머지 수는 또 (n-2)! ... (n-9)! 의 과정으로 순열의 모든 숫자를 구할 수 있게된다.
그렇다면 2번은 더 간단하다.
이미 순열을 알고있기 때문에, 맨 앞 숫자부터 차례대로 그 숫자만큼 더해주면 될 것이다.
예를 들어 n = 5 이고 주어진 순열이 5 4 3 2 1 이라고 하면,
첫 숫자 5를 보고, (n-1)번만큼 (n-1)! 를 더해주고
두 번째 4를 보고, (n-2)번만큼 (n-2)! 를 두해주는 과정을 반복한다.
이 때 1,2번 둘 다 공통으로 이미 마주친 숫자는 boolean 배열을 이용해 제외시켜야한다!!
코드를 보면서 이해해 보도록 하자.
소스 코드
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
|
#include <iostream>
#include <vector>
#include <algorithm>
#include <cstdbool>
using namespace std;
long long f[21] = { 1, };
bool check[21];
int main()
{
for (int i = 1; i <= 20; i++) {
f[i] = f[i - 1] * i;
}
int n; scanf("%d", &n);
int quest; scanf("%d", &quest);
if (quest == 1) {
long long k; scanf("%lld", &k);
vector<int> v(n);
for (int i = 0; i < n; i++) {
for (int j = 1; j <= n; j++) {
if (check[j] == true) continue;
if (f[n - i - 1] < k) {
k -= f[n - i - 1];
}
else {
v[i] = j;
check[j] = true;
break;
}
}
}
for (int i = 0; i < n; i++) {
printf("%d ", v[i]);
}
printf("\n");
}
else if (quest == 2) {
vector<int> v(n);
for (int i = 0; i < n; i++) {
scanf("%d", &v[i]);
}
long long ans = 0;
for (int i = 0; i < n; i++) {
for (int j = 1; j < v[i]; j++) {
if (check[j] == false) {
ans += f[n - i - 1];
}
}
check[v[i]] = true;
}
printf("%lld", ans + 1);
}
return 0;
}
|
cs |
위에서 설명한 그대로 1,2번을 나누고 아까의 과정을 코드로 표현하였다.
check 배열은 이미 사용된 숫자를 다시 쓰지 않기 위해 만든 boolean 배열이다.
코드로 이해가 안될 수 있으니 직접 손으로 과정을 써가면서 이해하는 것을 추천한다.
개발환경 : Visual Studio 2019
지적, 조언 언제든지 환영입니다~~
'백준 문제풀이' 카테고리의 다른 글
백준 1011번 Fly me to the Alpha Centauri (0) | 2020.01.06 |
---|---|
백준 2775번 부녀회장이 될테야 (0) | 2020.01.06 |
백준 10972,10973,10974 수열 구하기 문제 (0) | 2020.01.06 |
백준 6603번 로또 (0) | 2020.01.06 |
백준 10971번 외판원 순회2 (1) | 2020.01.03 |