【洛谷P4302】字符串折叠
题目
题目链接:https://www.luogu.com.cn/problem/P4302
折叠的定义如下:
- 一个字符串可以看成它自身的折叠。记作 S = S
- X(S)是X(X>1)个S连接在一起的串的折叠。记作 X(S) = SSSS…S(X个S)。
- 如果A = A’, B = B’,则AB = A’B’ 例如,因为3(A) = AAA, 2(B) = BB,所以3(A)C2(B) = AAACBB,而2(3(A)C)2(B) = AAACAAACBB
给一个字符串,求它的最短折叠。例如AAAAAAAAAABABABCCD的最短折叠为:9(A)3(AB)CCD。
思路
设 \(f[i][j]\) 表示 \([i,j]\) 这一段的最小折叠。那么 \(f[i][j]\) 可能会有两种转移:
- 枚举一个中间点 \(k\),\(f[i][j]=\min(f[i][k]+f[k+1][j])\)。
- 枚举一个中间点 \(k\),\([i\sim k]\) 正好是 \((k\sim j]\) 的前缀,且可以经过若干次自我复制得到 \((k\sim j]\)。
对于第二个转移,我们需要在较优秀复杂度内判断一段区间是否是另一段区间前缀且可以自我复制变成另一段区间。用字符串 hash 乱搞即可。
时间复杂度 \(O(n^3)\)。
代码
#include <bits/stdc++.h>
using namespace std;
typedef unsigned long long ull;
const int N=110;
const ull base=131;
int n,f[N][N],cnt[N];
ull power,g[N][N],hash[N][N];
char ch[N];
int main()
{
scanf("%s",ch+1);
n=strlen(ch+1);
memset(f,0x3f3f3f3f,sizeof(f));
power=1;
for (int i=1;i<=n;i++)
{
cnt[i]=cnt[i/10]+1;
g[i][1]=1; power=power*base;
for (int j=i*2;j<=n;j+=i)
g[i][j/i]=g[i][j/i-1]*power+1ULL;
f[i][i]=1;
}
for (int i=1;i<=n;i++)
for (int j=i;j<=n;j++)
hash[i][j]=hash[i][j-1]*base+ch[j];
for (int i=n;i>=1;i--)
for (int j=i+1;j<=n;j++)
for (int k=i;k<j;k++)
{
f[i][j]=min(f[i][j],f[i][k]+f[k+1][j]);
int len1=k-i+1,len2=j-k;
if (len2%len1==0 && hash[i][k]*g[len1][len2/len1]==hash[k+1][j])
f[i][j]=min(f[i][j],f[i][k]+2+cnt[len2/len1+1]);
}
printf("%d",f[1][n]);
return 0;
}