포스트

백준 1054번 팰린드롬 문장

문제 링크

자력솔이다. 예전에 한 번 보고 “아, 비트DP구나”만 생각하고 묵혀놨던 문제다.

어떤 단어를 회문의 중심으로 잡고, 왼쪽과 오른쪽에 단어를 추가하며 비트DP를 할 것이다. 중심을 기준으로 왼쪽 문자열을 $L$, 오른쪽 문자열을 $R$라고 하자. $n(L) \lt n(R)$라면 $L$에 단어를 추가하고, 아니라면 $R$에 단어를 추가할 것이다. 단어를 추가한다면, 반대쪽 문자열과 대응되는지를 확인하고 추가해야 한다. 이때 양쪽 문자열 중 길이가 더 긴 문자열이 중요함을 알 수 있다.

이제 DP식을 정의하면 다음과 같다:

\[DP[bit][i][j][d] = \text{변수의 정의가 아래와 같을 때 단어를 추가하여 회문을 만들 수 있는 경우의 수}\]
  • $bit$ : 사용한 단어의 집합 (비트마스트)
  • $d \in {L, R}$ 문자열이 $j$만큼 더 길다.
  • $d$에 끝에 $i$번 단어가 있다.

단어를 추가할 때 2 경우로 나눌 수 있다.

  1. 추가한 단어가 완전히 대응됨.
  2. 추가한 단어가 완전히 대응되지 않음.

1번 경우를 만족하는 문자열의 집합을 $P$, 2번 경우를 만족하는 문자열의 집합을 $Q$라고 한다면 DP식은 다음과 같다.

\(DP[bit][i][0][d] = \sum_{x \in bit^c}DP[bit \cup x][x][n(x)][L] + 1\) \(DP[bit][i][j][d] = \sum_{x \in P}DP[bit \cup x][i][j-n(x)][d] + \sum_{x \in Q}DP[bit \cup x][x][n(x)-j][\{L, R\} \setminus \{d\}]\)

배열의 크기를 1 작게 잡아서 1틀 했다.

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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
#include <bits/stdc++.h>

using namespace std;

using ll = long long;
const int MAXN = 13;

int N;
string s[MAXN];
ll cache[1<<MAXN][MAXN+1][MAXN+1][2];

bool checkin(const string& a, const string& b, int i, int left)
{
    int n = a.size();
    int m = b.size();
    if(b.size() > i) return false;
    if(left) {
        auto s = a.substr(0, i);
        s = s.substr(s.size() - m);
        reverse(s.begin(), s.end());
        return s == b;
    } else {
        auto s = a.substr(n-i);
        s = s.substr(0, m);
        reverse(s.begin(), s.end());
        return s == b;
    }
}
bool checkextend(const string& a, const string& b, int i, int left)
{
    int n = a.size();
    int m = b.size();
    if(b.size() <= i) return false;
    if(left) {
        auto s = a.substr(0, i);
        auto ss = b.substr(0, i);
        reverse(s.begin(), s.end());
        return s == ss;
    } else {
        auto s = a.substr(n-i);
        auto ss = b.substr(m-i);
        reverse(s.begin(), s.end());
        return s == ss;
    }
}

ll dp(int taken, int i, int j, int left)
{
    ll& ret = cache[taken][i][j][left];
    if(ret != -1) return ret;
    ret = 0;
    if(j == 0) {
        ret = 1;
        for(int k = 0; k < N; k++) {
            if(taken&(1<<k)) continue;
            ret += dp(taken|(1<<k), k, s[k].size(), 0);
        }
        return ret;
    }
    int m = s[i].size();
    for(int k = 0; k < N; k++) {
        if(taken&(1<<k)) continue;
        int m = s[k].size();
        if(checkin(s[i], s[k], j, left)) ret += dp(taken|(1<<k), i, j-m, left);
        if(checkextend(s[i], s[k], j, left)) ret += dp(taken|(1<<k), k, m-j, !left);
    }
    return ret;
}

int main()
{
    memset(cache, -1, sizeof(cache));
    cin >> N;
    for(int i = 0; i < N; i++) cin >> s[i];

    ll ans = 0;

    for(int i = 0; i < N; i++) {
        int n = s[i].size();
        for(int j = 0; j < n; j++) {
            int c = -1;
            int l = j;
            int r = j;
            bool possible = true;
            while(l >= 0 && r < n) {
                if(s[i][l] != s[i][r]) {
                    possible = false;
                    break;
                }
                c += 2;
                l--; r++;
            }
            if(possible) ans += dp(1<<i, i, n-c, l >= 0);
        }
        for(int j = 0; j < n; j++) {
            int c = 0;
            int l = j-1;
            int r = j;
            bool possible = true;
            while(l >= 0 && r < n) {
                if(s[i][l] != s[i][r]) {
                    possible = false;
                    break;
                }
                c += 2;
                l--; r++;
            }
            if(possible) ans += dp(1<<i, i, n-c, l >= 0);
        }
    }

    cout << ans;
}
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.