区间dp–P4302 [SCOI2003]字符串折叠
传送门
题目描述
折叠的定义如下:
一个字符串可以看成它自身的折叠。记作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。
输入格式
仅一行,即字符串S,长度保证不超过100。
输出格式
仅一行,即最短的折叠长度。
输入输出样例
输入 #1 复制
NEERCYESYESYESNEERCYESYESYES
输出 #1 复制
14
说明/提示
一个最短的折叠为:2(NEERC3(YES))
题解
1.算法思路
(1)dp核心:
看到区间求解会顺理成章的想到区间dp,将问题转换成多个子问题求解
根据区间dp通过子问题求解的特性
我们可以写出换与不换两种dp方程,如下
//k是区间i到j中间枚举出的一个点
dp[i][j]=min(dp[i][j],dp[i][k]+2+solve(l/(k-i+1)));
//2是括号的长度,solve是用来求系数的长度,比如系数为39,solve就会返回2
dp[i][j]=min(dp[i][j],dp[i][k]+dp[k+1][j]);//直接通过子问题转移求解
这里我们需要注意的是不一定折叠了就会更优,如 aaa折叠为3(a)就会更长
(2)check:
我们的第一个dp的使用是有条件的---------能够折叠
所以在dp过程中我们需要先check一下能否折叠
int check(int x1,int y1,int x2,int y2)
{
if((y2-x1+1)%(y1-x1+1)!=0) return 0;//判断能否被折叠成这个长度为y1-x1+1的片段
int h=y1-x1+1;//将要折叠成为的长度
for(int i=x2;i<=y2;i++)
{
if(v[i]!=v[i-h]) return 0;
//这里其实还是有一些投机的,不需要改变数组初始位置以及取模来与第一个模板比较,而是可以层层check
}
return 1;//如果可以就return true
}
(3)dp初始化:这是一个很重要的细节点,单列一波。
因为求小,先直接memset为较大数。
然后因为每个区间长度唯一的dp数组都在开始一定为1(因为只一个字符嘛)
然后dp[i][j]因为表示的是从i到j的区间内的字符串最短长度,所以应该初始化为初始的字符串长度,即j-i+1
2.附上AC代码+注释
#include<cstdio>
#include<iostream>
#include<stack>
#include<algorithm>
#include<cmath>
#include<cstring>
#define maxn 105
#define ll long long int
using namespace std;
char v[maxn];
int dp[maxn][maxn];
int check(int x1,int y1,int x2,int y2)
{
if((y2-x1+1)%(y1-x1+1)!=0) return 0;
int h=y1-x1+1;
for(int i=x2;i<=y2;i++)
{
if(v[i]!=v[i-h]) return 0;
}
return 1;
}
int solve(int k)
{
int su=0;
while(k)
{
su++;
k/=10;
}
return su;
}
int get[105];
int len;
int main()
{
memset(dp,0x3f,sizeof(dp));
cin>>v;
len =strlen(v);
for(int i=0;i<len;i++)
dp[i][i]=1;
for(int l=2;l<=len;l++)
{
for(int i=0,j;( j=l+i-1)<len;i++)
{
dp[i][j]=j-i+1;
for(int k=i;k<j;k++)
{
k-i+1;
if(check(i,k,k+1,j))
{
dp[i][j]=min(dp[i][j],dp[i][k]+2+solve(l/(k-i+1)));
}
else
{
dp[i][j]=min(dp[i][j],dp[i][k]+dp[k+1][j]);//这里也可以拿出去,不过更加麻烦
}
}
}
}
cout<<dp[0][len-1];
return 0;
}