완전 순열

정의

완전 순열(Complete Permutation), 또는 교란 순열(Derangement) 은 순열의 일종으로, 모든 원소의 위치를 바꾸는 순열이다.

집합 SS의 순열 σ:S→S\sigma: S \to S 가 모든 s∈Ss \in S에 대해 σ(s)≠s\sigma(s) \neq s를 만족시키면, σ\sigma를 완전 순열이라고 한다.

nn 개 원소를 배열하는 순열의 수를 우리는 계승(팩토리얼)이라고 하고, n!n!이라고 표현하였다. nn개 원소에 대한 완전순열의 수는 준계승(Subfactorial) 이라고 하고, !n!n이라고 표현한다.

!n!n을 DnD_n이라고 하고, 완전 순열의 점화식을 구해보자.

완전 순열의 점화식 DnD_n 유도하기

1부터 nn까지의 자연수를 두 줄 쓴다.

윗 줄의 숫자들을 하나하나씩 대응할 때, 자기 자신을 제외한 다른 숫자로 대응 시키는 것이 완전 순열이다.

숫자의 갯수가 nn 개일 때, 이 대응하는 방법의 수를 DnD_n이라 하자.

1부터 nn까지의 수 중 임의의 수 aa를 하나 뽑아 버리자.

aa를 자기 자신을 제외한 다른 n−1n-1개의 수 중 하나와 대응시켜야 한다. 이 대응시키는 수를 kk라고 하자. (k≠a)(k \neq a)

그러면 kk의 관점에서는 다음 두 선택지가 있다.

aa에 대해 kk를 선택하는 가짓수가 n−1n-1이므로, 최종 점화식은1

[완전 순열의 점화식]

Dn=(n−1)(Dn−2+Dn−1) (n≥2)D_n = (n-1)(D_{n-2} + D_{n-1}) \ (n \geq 2)

그리고, D0=1D_0 = 1, D1=0D_1 = 0이다.

이다.

a=1a=1일 때, 다이어그램으로 표현

a=1a = 1일때, 다이어그램으로 표현하면 아래와 같다.

먼저, 11도 kk에, kk도 11에 대응된 상황이다.

11과 kk를 제외한 나머지 n−2n-2개의 완전 순열을 구하는 문제로 환원된다.

그리고, 11은 kk에 대응되었으나, kk는 11에 대응되지 않은 상황이다.

이러면 kk는 11에 대응되면 안 되는 상황이므로, kk를 마치 11인 것처럼 취급하여, 이미 짝이 정해진 11을 제외한 나머지 n−1n-1개의 완전 순열을 구하는 문제로 환원된다.

완전 순열 알고리즘

점화식으로 표현될 수 있으므로, nn까지의 완전 순열의 개수를 동적 계획법을 이용해 쉽게 계산할 수 있다.

대표적인 완전순열 문제로 백준 1947 G3 선물 전달이 있다.

이 문제의 코드는 아래와 같이, 위에서 계산한 점화식을 그대로 사용하면 된다.

#include <iostream>
#include <vector>

using namespace std;

using ll = long long;

int main() {
  int N; cin >> N; // [10^6]

  vector<ll> dp(N + 1);

  // 초항 계산
  dp[0] = 1; 
  dp[1] = 0;

  for (int n = 2; n <= N; ++n) {
    // mod를 여기서 취하지 않으면 오버플로우가 발생한다.
    // 점화식으로 dp 계산
    dp[n] = (n - 1) * (dp[n - 1] + dp[n - 2]) % 1'000'000'000ll;
  }

  cout << dp[N];
}

참고 문헌

Footnotes

  1. 임의의 aa에 대해 모두 생각하는 것이 아니라, aa를 하나 뽑아서 고정해 버리는 것이고, 그 aa에 대해 kk를 몇 개 뽑느냐가 분기되는 것 임에 주의한다. aa가 실제로 어떤 값인지는 전혀 중요하지 않으며, 그냥 하나의 항을 임의로 처리해 버리려면 kk를 선택해야 하는데, 여기서 분기가 n−1n-1개 생긴다고 생각하는 것이 좋다. ↩

← 글 목록