본문으로 건너뛰기
김신건의 로그

Young Tableau: 삽입 정렬형 격자, LIS

· 수정 · 📖 약 4분 · 1,212자/단어 #algorithm #data-structure #young-tableau #lis #combinatorics
Young Tableau, 영 태블로, Young diagram, RSK correspondence, Schensted insertion

정의

Young Tableau 는 정수를 격자(grid) 에 배치한 자료구조로, 다음 조건을 만족합니다:

  • 각 행(row): 왼쪽에서 오른쪽으로 비감소 (non-decreasing)
  • 각 열(column): 위에서 아래로 비감소 (non-decreasing)
표준 Young Tableau 예시 (행/열 모두 증가):
  1  3  5
  2  4  8
  6  7

Standard Young Tableau (SYT): 1 부터 n 까지 각 숫자가 정확히 한 번씩 등장하며 행/열이 증가.

Semistandard Young Tableau (SSYT): 행은 비감소, 열은 순증가.

RSK 대응 (Robinson-Schensted-Knuth)

RSK 대응 은 순열 (또는 수열) 과 Young Tableau 쌍 (P, Q) 사이의 일대일 대응입니다.

  • P: 삽입 태블로 (Insertion Tableau) - 원소를 Schensted 삽입으로 구성
  • Q: 기록 태블로 (Recording Tableau) - 삽입 순서를 기록

핵심 성질:

  • 수열의 LIS 길이 = P 의 첫 행 길이
  • 수열의 LDS (Longest Decreasing Subsequence) 길이 = P 의 첫 열 길이

시각화

수열 [3, 1, 4, 1, 5] 의 RSK 삽입 과정:

flowchart TD
    A["insert 3: row0={3}"]
    A --> B["insert 1: 1 bumps 3<br/>row0={1}, row1={3}"]
    B --> C["insert 4: fits at end of row0<br/>row0={1,4}, row1={3}"]
    C --> D["insert 1: 1 bumps 4, 4 to row1<br/>row0={1,1}, row1={3,4}"]
    D --> E["insert 5: fits at end of row0<br/>row0={1,1,5}, row1={3,4}"]

첫 행 길이 = 3 = LIS 길이 (예: [1, 4, 5] 또는 [1, 1, 5]).

Schensted 삽입 규칙 (행 i 에 값 v 삽입):

flowchart LR
    Start["행 i 에 v 삽입"]
    Start --> Q1{"행 i 에 v 보다 큰 원소 있음?"}
    Q1 -->|"없음"| Append["v 를 행 i 끝에 추가<br/>(삽입 완료)"]
    Q1 -->|"있음"| Bump["첫 번째 v 초과 원소 x 를 v 로 교체<br/>x 를 행 i+1 에 재귀 삽입"]
    Bump --> Start2["행 i+1 에 x 삽입"]
    Start2 --> Q1

핵심 아이디어

Schensted 삽입 (Row Bumping)

값 v 를 태블로 P 에 삽입하는 과정:

insert(P, v):
    for i = 0, 1, 2, ...:
        if i == len(P):
            P.append([v])   // 새 행 추가
            return
        // 행 i 에서 v 보다 큰 첫 번째 원소 찾기
        j = first index where P[i][j] > v
        if j == len(P[i]):
            P[i].append(v)  // 행 끝에 추가
            return
        // bump: P[i][j] 를 v 로 교체, 기존 값을 다음 행으로
        v, P[i][j] = P[i][j], v
        // v (= 기존 P[i][j]) 를 행 i+1 에 삽입

Young Tableau 의 힙 성질

Young Tableau 는 2D 힙으로도 볼 수 있습니다:

  • 최솟값: 항상 (0, 0) 위치
  • 최솟값 추출: (0, 0) 을 제거하고 재구성 O(m + n)
  • 삽입: Schensted 삽입 O(m + n)

m x n 크기 Young Tableau 에서 k 번째 최솟값을 O(k log(m+n)) 에 찾을 수 있습니다.

LIS 와의 관계

LIS 길이를 Young Tableau 로 구하는 방법:

LIS_via_RSK(seq):
    P = []  // 빈 태블로
    for v in seq:
        insert(P, v)
    return len(P[0])  // 첫 행 길이

이는 patience sorting 과 동일합니다. 각 행의 마지막 원소들이 patience sorting 의 더미 (pile) 상단과 같습니다.

알고리즘

삽입 (Schensted Insertion)

schensted_insert(P, v):
    for i in 0..len(P):
        if i == len(P):
            P.append([v]); return
        // upper_bound: v 보다 큰 첫 번째 위치
        j = upper_bound(P[i], v)
        if j == len(P[i]):
            P[i].append(v); return
        P[i][j], v = v, P[i][j]  // bump

최솟값 추출

extract_min(P):
    result = P[0][0]
    // 오른쪽 아래 방향으로 재구성
    i, j = 0, 0
    while True:
        right = P[i][j+1] if j+1 < len(P[i]) else INF
        down  = P[i+1][j] if i+1 < len(P) else INF
        if right == INF and down == INF:
            P[i].pop()
            if not P[i]: P.pop()
            break
        if right <= down:
            P[i][j] = right; j += 1
        else:
            P[i][j] = down; i += 1
    return result

정렬 (Young Tableau Sort)

m x n Young Tableau 에서 mn 개 원소를 정렬: 최솟값을 mn 번 추출. O(mn * (m+n)).

구현

// Young Tableau: RSK 삽입 + LIS 길이
#include <bits/stdc++.h>
using namespace std;

// Schensted 삽입: P 에 v 삽입
void insert(vector<vector<int>>& P, int v) {
  for (int i = 0; ; i++) {
      if (i == (int)P.size()) {
          P.push_back({v});
          return;
      }
      // v 보다 큰 첫 번째 원소 위치 (upper_bound)
      auto it = upper_bound(P[i].begin(), P[i].end(), v);
      if (it == P[i].end()) {
          P[i].push_back(v);
          return;
      }
      // bump: *it 를 v 로 교체, 기존 *it 를 다음 행으로
      swap(*it, v);
  }
}

// LIS 길이 = RSK 후 첫 행 길이
int lis_length(vector<int>& seq) {
  vector<vector<int>> P;
  for (int v : seq) insert(P, v);
  return P.empty() ? 0 : (int)P[0].size();
}

// 태블로 출력
void print_tableau(vector<vector<int>>& P) {
  for (auto& row : P) {
      for (int x : row) cout << x << " ";
      cout << "\n";
  }
}

int main() {
  ios::sync_with_stdio(0); cin.tie(0);
  int n; cin >> n;
  vector<int> seq(n);
  for (int& x : seq) cin >> x;

  vector<vector<int>> P;
  for (int v : seq) insert(P, v);

  cout << "LIS length: " << P[0].size() << "\n";
  cout << "Insertion Tableau:\n";
  print_tableau(P);
}
stdin
5
3 1 4 1 5
결과
LIS length: 3
Insertion Tableau:
1 1 5 
3 4

복잡도

항목
Schensted 삽입O(m + n) (m x n 태블로)
최솟값 추출O(m + n)
LIS 계산 (n 원소)O(n log n) (이진 탐색 사용 시)
공간O(n)

Schensted 삽입에서 각 행에서 이진 탐색을 쓰면 O(log n) 이지만, 행 수가 O(n) 이므로 전체는 O(n log n).

변형 / 활용

응용설명
LIS첫 행 길이 = LIS 길이. Patience sorting 과 동일.
LDS첫 열 길이 = LDS (Longest Decreasing Subsequence) 길이.
Dilworth 정리최소 체인 분해 = 최대 반체인 크기 (LIS 와 관련).
2D 힙최솟값 추출 O(m+n). 우선순위 큐 대안.
표현론대칭군의 기약 표현과 일대일 대응.

함정

WARNING

Young Tableau 구현 시 자주 발생하는 실수들.

1. upper_bound vs lower_bound

Schensted 삽입에서 upper_bound (v 보다 큰 첫 번째 위치) 를 써야 합니다. lower_bound (v 이상인 첫 번째 위치) 를 쓰면 같은 값이 있을 때 잘못 bump 됩니다.

SSYT (Semistandard) 에서는 열이 순증가이므로 같은 값을 같은 열에 넣으면 안 됩니다. 이 경우 lower_bound 를 씁니다.

2. 빈 태블로 처리

n = 0 이거나 빈 수열일 때 P 가 비어 있습니다. P[0].size() 접근 전에 빈 체크가 필요합니다.

3. LIS 와 patience sorting 의 차이

Patience sorting 의 더미 상단 배열은 Young Tableau 의 각 행 마지막 원소와 같습니다. 하지만 실제 LIS 를 복원하려면 추가 정보가 필요합니다.

4. 행/열 조건 혼동

Standard Young Tableau: 행/열 모두 순증가. Semistandard Young Tableau: 행은 비감소, 열은 순증가. RSK 삽입으로 만들어지는 것은 SSYT 입니다.

BOJ 연습 문제

번호제목설명
BOJ 12015가장 긴 증가하는 부분 수열 2LIS O(n log n)
BOJ 14003가장 긴 증가하는 부분 수열 5LIS 복원
BOJ 2568전깃줄 2LIS 응용

관련 위키

이 글의 용어 (4개)
순열 사이클 분해 (Permutation Cycle Decomposition)algorithm
정의 순열 사이클 분해는 순열 p[1..N]을 서로소(disjoint)인 사이클들의 곱으로 표현하는 것. 각 사이클 (a1 a2 ... ak) 는 a1 -> p[a1] = a2,…
조합론 (Combinatorics)algorithm
정의 조합론 (Combinatorics) 은 유한 집합의 원소를 세는 수학 분야. PS 에서는 주로 순열 (Permutation), 조합 (Combination), 이항계수 (B…
최장 증가 부분 수열 (Longest Increasing Subsequence)algorithm
정의 최장 증가 부분 수열 (Longest Increasing Subsequence, LIS) 은 주어진 수열에서 순서를 유지하며 선택한 부분 수열 중 엄격히 증가하는 가장 긴 …
Priority Queue / Heap: 우선순위 큐algorithm
정의 Priority Queue 는 우선순위가 가장 높은 원소를 O(log N) 에 pop 할 수 있는 자료구조. Binary Heap 이 표준 구현. - Max-heap: 부모…

💬 댓글

사이트 검색 / 명령어

검색

스크롤 = 확대/축소 · 드래그 = 이동 · 0 = 원래 크기 · ESC = 닫기