본문 바로가기

BOJ 31028 순열의 개수

수정

문제 링크

나는코더다 2023 송년대회에 kongum 님, mingyu331 님과 참가하여 이 문제만 kongum 님과 5시간 동안 고민했습니다. 여기서 소개할 풀이는 kongum 님이 대회 후에 떠올린 풀이입니다. 대회 이후 풀이의 발견에 dohoon 님이 도움을 주셨습니다.

두 순열의 접두사를 이어붙여 순열을 만들어야 합니다. 접두사를 이어붙이는 방법은 (N+1)2(N+1)^2가지가 있으므로, 여러 접두사 중 순열로 가능한 경우를 빠르게 추리는 방법을 찾아야 합니다.

가장 먼저 할 수 있는 관찰은 문제에서 AABB를 바꾸어 풀어도 답이 같다는 것입니다. 바로 도움이 되진 않지만 짚고 넘어갑시다.

다음 관찰로, 어떤 수열이 순열이려면 다음 조건을 만족해야 합니다.

이를 바탕으로 AA의 각 접두사를 보면서, BB의 접두사 중 순열을 이룰 수 있는 것을 추리는 전략을 세워 보겠습니다.

AABB를 바꾸어 풀어도 답이 같으므로, AA의 접두사에 최댓값이 있다고 강제해도 문제를 해결할 수 있습니다. AABB를 바꾸어 같은 방법을 한 번 더 적용하면 됩니다.

현재 AA의 접두사와 겹치는 원소가 없으면서 길이가 현재 AA의 접두사 내 최댓값에 11을 더한 값인 BB의 접두사가 있는지 판정하면 됩니다. 현재 AA의 접두사 내 최댓값은 고정되어 있으므로, 순열을 이루기 위해 확인할 BB의 접두사가 하나로 고정됩니다.

문제는 확인할 BB의 접두사와 현재 AA의 접두사에 겹치는 원소가 있는지 빠르게 판정하기 어렵다는 것입니다. 하지만 AA의 접두사를 짧은 것부터 본다면, 현재 AA의 접두사와 원소가 겹치지 않기 위한 BB의 접두사 길이의 범위를 관리할 수 있습니다. AA의 접두사가 길어질수록 가능한 BB의 접두사 길이가 짧아지기 때문입니다. 두 포인터를 이용하여 amortized O(1)\mathcal{O}(1)에 가능한 가장 긴 BB의 접두사 길이를 구할 수 있습니다.

AA의 접두사에 최댓값이 있다고 강제했으므로 BB의 접두사에 최댓값이 없다는 것을 보장해야 합니다. AA의 접두사의 최댓값은 증가하고, 이에 따라 가능한 BB의 접두사 길이 범위는 감소합니다. 두 포인터를 이용하여 amortized O(1)\mathcal{O}(1)에 가능한 가장 긴 BB의 접두사 길이를 구할 수 있습니다.

따라서 순열을 이룰 수 있는 BB의 접두사 길이가 두 포인터를 사용해 구한 두 길이 이하라면 답이 11을 더해주면 됩니다.

전체 시간복잡도는 O(N)\mathcal{O}(N)입니다.

function solve(N, A, B)

  1. return count_perm(N, A, B) + count_perm(N, B, A) + 1

function count_perm(N, A, B)

  1. max_A := 0 현재 A의 최대 원소

  2. viewed = ∅ B의 접두사에 있는 원소의 집합

  3. until_viewed := N A와 원소가 겹치지 않도록 하는 가장 긴 B의 접두사 길이

  4. until_max := 0

    A의 접두사에 최댓값이 있도록 하는 가장 긴 B의 접두사 길이

  5. max_B := 0 A의 접두사에 최댓값이 있도록 하는 가장 긴 B의 최대 원소

  6. count := 0
  7. for L from 1 upto N

    1. max_A := max(max_A, A[L])
    2. while A[L] not in viewed

      1. add B[until_viewed] to viewed

      2. until_viewed := until_viewed - 1
    3. while max_B < max_A and B[until_max] < max_A

      1. max_B := max(max_B, B[until_max])
      2. until_max := until_max + 1
    4. if max_A - L < min(until_viewed, until_max)

      1. count := count + 1
  8. return count