[백준] 18439 LCS 6 C++

2024. 12. 24. 14:06·코딩/백준-알고리즘

효율적인 비트셋 기반 LCS(Largest Common Subsequence) 구현

두 문자열 사이의 최장 공통 부분 수열(Longest Common Subsequence, LCS)은 컴퓨터 과학에서 자주 등장하는 문제입니다. 일반적인 동적 계획법(DP)을 활용한 방식은 O(n*m)의 시간 복잡도를 가지며, 두 문자열의 길이가 클수록 메모리 사용량과 실행 시간이 증가합니다.

 

1. 문제 정의

주어진 두 문자열 a와 b에 대해:

  • 공통으로 존재하는 문자의 순서를 유지하는 가장 긴 부분 수열의 길이를 계산해야 합니다.

예를 들어:

  • a = "ACDBE", b = "ABCDE"가 주어졌을 때, 두 문자열의 LCS는 "ACDE"이며 길이는 4입니다.

2. 알고리즘의 핵심 아이디어

비트셋 기반 LCS 알고리즘은 아래의 원리로 작동합니다:

  1. 비트셋(bset) 활용:
    • 각 문자열의 문자를 비트로 매핑합니다.
    • 문자열 b의 각 문자를 기준으로 비트셋을 생성해, 각 문자의 위치를 비트로 표현합니다.
  2. 동적 계획법의 행렬 계산 대체:
    • 기존의 2차원 DP 테이블 대신, 비트 연산을 활용하여 효율적으로 상태를 업데이트합니다.
  3. 비트 연산의 효율성 활용:
    • |, &, << 등의 비트 연산으로 LCS를 계산하며, 반복문이 CPU에 최적화된 방식으로 수행됩니다.

3. 코드 설명

3.1. bset 구조체

코드에서 bset은 비트셋을 다루기 위한 커스텀 자료구조입니다. 이는 C++ STL의 bitset과 유사하지만, 멀티워드(다중 64비트)를 지원하여 50,000비트 이상의 대규모 비트를 다룰 수 있습니다.

 
struct bset {
    using word = uint64_t;         // 64비트 단위
    static const int WORD_SIZE = 64;
    static const int TABLE_SIZE = 800; // 최대 50,000 비트 표현 가능

    word table[TABLE_SIZE];        // 비트셋 데이터

    // 기본 생성자: 모든 비트를 0으로 초기화
    bset() {
        memset(table, 0, sizeof(table));
    }

    // 특정 인덱스 비트를 1로 설정
    inline void setBit(int pos) {
        table[pos >> 6] |= (1ULL << (pos & 63));
    }
};

3.2. 비트셋 초기화

먼저, 문자열 b의 각 문자를 기반으로 비트셋을 구성합니다. b[j]가 특정 알파벳이라면, 그 위치에 해당하는 비트를 1로 설정합니다.

for (int j = 0; j < m; j++) {
    A[b[j] - 'A'].setBit(j);
}

A는 26개의 비트셋 배열로, 각 알파벳(A~Z)에 대한 비트셋을 저장합니다.

3.3. 비트셋을 이용한 LCS 계산

  1. 초기화: bset B는 DP 상태를 표현하며, 현재까지의 LCS 정보를 저장합니다.
  2. 갱신:
    • X = B | A[a[i] - 'A']:
      • 현재 문자를 포함하는 경우의 상태.
    • Y = (B << 1) | 1:
      • 이전 상태를 한 칸 이동하며, 첫 번째 비트를 설정.
    • B = X & ~(X - Y):
      • 비트 연산으로 LCS 갱신.
bset B;
for (int i = 0; i < n; i++) {
    bset X = B | A[a[i] - 'A'];
    bset Y = (B << 1) | 1ULL;
    B = X & ~(X - Y);
}

3.4. 결과 계산

최종적으로 B에 포함된 비트 1의 개수가 LCS의 길이를 나타냅니다. 이는 __builtin_popcountll()을 사용하여 빠르게 계산합니다.

long long ans = 0;
int limit = (m + 63) >> 6; // 필요한 word 개수
for (int i = 0; i < limit; i++) {
    ans += __builtin_popcountll(B.table[i]);
}

4. 시간 및 공간 복잡도

4.1. 시간 복잡도

  • 비트 연산이 문자열의 길이에 따라 O(n * m / 64)로 수행됩니다.
  • 일반적인 DP 방식의 O(n * m)보다 훨씬 빠르게 처리할 수 있습니다.

4.2. 공간 복잡도

  • 문자열의 최대 길이를 기준으로 O(m / 64)의 메모리를 사용합니다.
  • 기존 2차원 DP 배열(O(n * m))보다 메모리 사용량이 크게 줄어듭니다.

5. 코드 전체

#include <bits/stdc++.h>
using namespace std;

struct bset {
    using word = uint64_t;
    static const int WORD_SIZE = 64;
    static const int TABLE_SIZE = 800;

    word table[TABLE_SIZE];

    bset() { memset(table, 0, sizeof(table)); }

    inline void setBit(int pos) {
        table[pos >> 6] |= (1ULL << (pos & 63));
    }

    inline bset operator|(const bset &other) const {
        bset ret;
        for (int i = 0; i < TABLE_SIZE; i++) {
            ret.table[i] = table[i] | other.table[i];
        }
        return ret;
    }

    inline bset operator<<(int shift) const {
        bset ret;
        for (int i = TABLE_SIZE - 1; i >= 0; i--) {
            ret.table[i] = (table[i] << shift);
            if (i > 0) ret.table[i] |= (table[i - 1] >> (64 - shift));
        }
        return ret;
    }

    inline bset operator-(const bset &other) const {
        return *this + (~other + 1ULL);
    }

    inline bset operator&(const bset &other) const {
        bset ret;
        for (int i = 0; i < TABLE_SIZE; i++) {
            ret.table[i] = table[i] & other.table[i];
        }
        return ret;
    }
};

static const int ALPH = 26;
bset A[ALPH];

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    string a, b;
    cin >> a >> b;
    if (a.size() > b.size()) swap(a, b);

    int n = a.size(), m = b.size();
    for (int j = 0; j < m; j++) A[b[j] - 'A'].setBit(j);

    bset B;
    for (int i = 0; i < n; i++) {
        bset X = B | A[a[i] - 'A'];
        bset Y = (B << 1) | 1ULL;
        B = X & ~(X - Y);
    }

    long long ans = 0;
    int limit = (m + 63) >> 6;
    for (int i = 0; i < limit; i++) ans += __builtin_popcountll(B.table[i]);

    cout << ans << "\n";
    return 0;
}
저작자표시 (새창열림)

'코딩 > 백준-알고리즘' 카테고리의 다른 글

[백준] 25954 LCS 9 C++,번외 PyPy3  (0) 2025.01.11
[백준] Round words Python 3  (0) 2025.01.01
[백준] 1046 그림자 Python 3  (0) 2024.06.21
[백준] 2586 소방차 Python 3  (0) 2024.06.19
[백준] 수열과 퀴리 13 Python 3 (Feat 비효율)  (0) 2024.06.15
'코딩/백준-알고리즘' 카테고리의 다른 글
  • [백준] 25954 LCS 9 C++,번외 PyPy3
  • [백준] Round words Python 3
  • [백준] 1046 그림자 Python 3
  • [백준] 2586 소방차 Python 3
안녕 나의 20대
안녕 나의 20대
회로설계 RnD에서 현업으로 일하고 있습니다 일하며 배운 것을 버리기 아까워 기록으로 남깁니다 이메일 njh208804@naver.com
  • 안녕 나의 20대
    우당탕탕 회로둥이
    안녕 나의 20대
  • 전체
    오늘
    어제
    • 분류 전체보기 (113)
      • 전자공학 (57)
        • 전자회로 (20)
        • 전자회로 - 심화 (9)
        • RF (21)
        • 회로 설계 (4)
        • 개인 설계 (2)
        • 논리회로 (1)
      • 코딩 (22)
        • 개인 PT (5)
        • 백준-알고리즘 (17)
      • 영어 공부 (1)
      • 잡담 (33)
        • 취미 혹은 주절 (10)
        • 매주 한 편 - 시 (23)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

    • 이메일
  • 공지사항

    • 보호 글 관련
    • 다크모드
  • 인기 글

  • 태그

    python3
    opamp
    13925 수열과 퀴리 13
    LED
    bypass capacitor
    i²c
    티스토리챌린지
    시
    2586 소방차
    두터움
    UART
    배선 폭
    logic ic
    엷음
    탄소저항
    prs 기타
    crystal unit
    MOSFET
    prs studio
    지렁이 게임
    수열과 퀴리 13
    바이패스 커패시터
    lcs 2
    전자회로
    TVS
    SPI
    prs 일렉기타
    paul reed smith guitars
    photocoupler
    오블완
    Flex Prime
    백준
    삽입손실
    스케메틱
    uart 통신
    latch-up
    코일 인덕터
    python
    스미스차트
    자기공진 주파수
    hot start cold start
    기타 오일
    prs
    high z
    python 3
    analog-to-digital converter
    1046 그림자
    RF
    phthon 3
    noise figure
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.1
안녕 나의 20대
[백준] 18439 LCS 6 C++
상단으로

티스토리툴바