原题链接:uva 1630 Folding
题目大意
给定一个字符串S,可以将其中一段元素折叠,条件是每段相同并且可以嵌套.例如可以将NEERCYESYESNEERCYESYESYES
折叠成2(NEERC3(YES))
.要求输出方案.
数据范围:
(1 leq n leq 100)
思路
由于n只有100,而且设计整个区间的操作.比较容易想到区间DP.
状态:(f[l][r])表示区间[l,r]最短可以压缩到多短.
入口:(f[l][r] = r - l + 1)即不压缩,就以自己为最终的答案.
转移:
转移有几种情况,分别考虑一下:
- 直接将区间划分成两半,从两半各自拼过来.这个时候方程是(f[l][r] = min(f[l][r],f[l][k] + f[k + 1][r])其中(kin [l,r-1]).为什么不是三段四段呢?因为两段就已经包含了继续下分的各种情况了,如果三段是比较短的,那么他必然包含在某个两段里的一个最后的答案.
- 这个区间是可以拆分的.即存在一个(m)是当前区间长度(len)的因数,并且每一段(m)长度的子串都是完全相同的,这个时候每个循环节的答案就是(f[l][l + m - 1]),而每个循环节要有(len / m)的数字在前面以及一对括号,数字可以预处理出来1~100的位数直接放进去用.总的转移方程就是(f[l][r] = min(f[l][r],f[l][l + m - 1] + numlen(len / m) + 2)
出口:(f[l][r])
不过这个题还要输出具体方案,那么对照着写一个(res[l][r])记录([l,r])区间能获得的最终的字符串的答案具体是谁就可以了.在转移更新答案的时候把新的答案给构造出来.
代码
#define _CRT_SECURE_NO_WARNINGS
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 125;
int numlen[N],f[N][N],n;
bool st[N][N];
string s,res[N][N];
bool check(int l,int r,int m)
{
for(int i = l;i < l + m;++i)
{
for(int j = i;j <= r;j += m)
{
if(s[j] != s[i])
return 0;
}
}
return 1;
}
inline void _ct(int l,int r)
{
for(int i = l;i <= r;++i)
res[l][r] += s[i];
}
int main()
{
ios::sync_with_stdio(0);cin.tie(0);
while(cin >> s)
{
n = s.size();s = "#" + s;
for(int i = 1;i <= 9;++i) numlen[i] = 1;
for(int i = 10;i <= 100;++i) numlen[i] = 2;
for(int len = 1;len <= n;++len)
{
for(int l = 1;l + len - 1 <= n;++l)
{
int r = l + len - 1;
f[l][r] = len;res[l][r] = s.substr(l,len);
for(int k = l;k < r;++k)
{
if(f[l][r] > f[l][k] + f[k + 1][r])
{
f[l][r] = f[l][k] + f[k + 1][r];
res[l][r] = res[l][k] + res[k + 1][r];
}
}
for(int m = 1;m < len;++m)
{
if(len % m) continue;
if(check(l,r,m))
{
if(f[l][r] > f[l][l + m - 1] + numlen[len / m] + 2)
{
f[l][r] = f[l][l + m - 1] + numlen[len / m] + 2;
res[l][r] = to_string(len / m) +
"(" + res[l][l + m - 1] + ")";
}
}
}
}
}
cout << res[1][n] << endl;
}
return 0;
}