思路
區間 DP 好題。定義 \(dp_{i,j}\) 表示將 \(s_{i \sim j}\) 摺疊能獲得的最短長度。
那麼,依舊是列舉一箇中間點 \(k\),那麼,我們將 \(dp_{i,j}\) 分為了 \(dp_{i,k}\) 和 \(dp_{k + 1,j}\) 兩部分。
對於這兩部分,可顯然,有:\(dp_{i,j} = \min(dp_{i,k} + dp_{k + 1,j})\)。
如果可以將 \([i,j]\) 區間合併,可以列舉摺疊後剩餘的字串的最後一位的下標 \(k\),然後判斷一下如果是 \(k\),能否摺疊,如果可以,則有:\(dp_{i,j} = \min(dp_{i,i + k - 1} + num_{cnt} + 2)\)。
其中,\(cnt\) 表示摺疊後字串中的數字,\(num_i\) 表示 \(i\) 數字的長度,\(2\) 表示括號。
遞推邊界顯然,對於所有的 \(dp_{i,i} = 1\)。
Code
#include <bits/stdc++.h>
#define re register
using namespace std;
const int N = 110,inf = 0x3f3f3f3f;
int n;
int num[N];
int dp[N][N];
string s;
inline void init(){
memset(dp,inf,sizeof(dp));
for (re int i = 1;i <= 9;i++) num[i] = 1;
for (re int i = 10;i <= 99;i++) num[i] = 2;
num[100] = 3;
for (re int i = 1;i <= n;i++) dp[i][i] = 1;
}
inline bool check(int l,int r,int x){
for (re int i = l + x;i <= r;i++){
int id = l + (i - l) % x;
if (s[i] != s[id]) return false;
}
return true;
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> s;
n = s.length();
s = ' ' + s;
init();
for (re int l = 2;l <= n;l++){
for (re int i = 1;i + l - 1 <= n;i++){
int j = i + l - 1;
for (re int k = i;k < j;k++) dp[i][j] = min(dp[i][j],dp[i][k] + dp[k + 1][j]);
for (re int k = 1;k < l;k++){
if (l % k == 0){
int cnt = l / k;
if (check(i,j,k)) dp[i][j] = min(dp[i][j],dp[i][i + k - 1] + num[cnt] + 2);
}
}
}
}
cout << dp[1][n];
return 0;
}