Suhwanc

문제 링크 : https://www.acmicpc.net/problem/1722

 

1722번: 순열의 순서

첫째 줄에 N(1≤N≤20)이 주어진다. 둘째 줄의 첫 번째 수는 소문제 번호이다. 1인 경우 k(1≤k≤N!)를 입력받고, 2인 경우 임의의 순열을 나타내는 N개의 수를 입력받는다. N개의 수에는 1부터 N까지의 정수가 한 번씩만 나타난다.

www.acmicpc.net

 

문제

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] == truecontinue;
                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

지적, 조언 언제든지 환영입니다~~