传送门(洛谷)
算法:区间动态规划
f [ i ] [ j ] f[i][j] f[i][j]表示处理 i 到 j i到j i到j这段区间所需要的最小代价
两种转移方法:
一.标准的区间
d
p
dp
dp的转移
f
[
i
]
[
j
]
=
m
i
n
(
f
[
i
]
[
j
]
,
f
[
i
]
[
k
]
+
f
[
k
+
1
]
[
j
]
)
;
f[i][j]=min(f[i][j],f[i][k]+f[k+1][j]);
f[i][j]=min(f[i][j],f[i][k]+f[k+1][j]);
二.如果
i
i
i到
j
j
j这段区间可以折叠
f
[
i
]
[
j
]
=
m
i
n
(
f
[
i
]
[
j
]
,
f
[
i
]
[
k
]
+
2
+
n
u
m
[
l
e
n
/
l
]
)
;
f[i][j]=min(f[i][j],f[i][k]+2+num[len/l]);
f[i][j]=min(f[i][j],f[i][k]+2+num[len/l]);
注意在可以折叠中的括号也算字符,前面的数字要进行预处理,看看是一位数还是两位数
其中
l
e
n
len
len表示
i
i
i到
j
j
j这段区间长度
l
e
n
len
len可以被循环节整除
l
l
l表示可以折叠的循环节的长度,也就是
i
i
i到
k
k
k的长度
也不用担心类似于 a b c b c b c abcbcbc abcbcbc这种情况,即使 b c bc bc这个循环节 2 2 2无法整除7,但是在处理小区间 2 − 7 2-7 2−7的位置的时候是可以折叠的,最后合并时就是取的折叠后的最优值
#include<bits/stdc++.h>
#define inf 100000000
#define rep(i,a,b) for(int (i)=(a);(i)<=(b);(i)++)
using namespace std;
const int maxn=1e5+10;
const int maxm=1e3+10;
int n;
char s[maxn];
int f[maxm][maxm],num[maxn];
template <class t> inline void read(t &x) {
x=0;int f=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-') f=-1;ch=getchar();}
while(isdigit(ch)){x=10*x+ch-'0';ch=getchar();}
x*=f;
}
void readdata() {
scanf("%s",s+1);
n=strlen(s+1);
memset(f,false,sizeof(f));
}
bool check(char s[],int len,int lenth) {
rep(i,lenth,len-1) {//我们从开始循环位置开始暴力判断,注意是到len-1的位置,因为len可以被lenth整除
if(s[i]!=s[i%lenth]) return false;
}
return true;
}
void init() {
memset(f,0x3f,sizeof(f));
rep(i,1,9) num[i]=1;
rep(i,10,99) num[i]=2;
rep(i,100,999) num[i]=3;
}
void work() {
init();
rep(i,1,n) f[i][i]=1;
rep(len,2,n)
rep(i,1,n-len+1) {
int j=i+len-1;
rep(k,i,j-1) {
f[i][j]=min(f[i][j],f[i][k]+f[k+1][j]);
}
rep(k,i,j-1) {
int l=k-i+1;
if(len%l!=0) continue;
if(check(s+i,len,l))
f[i][j]=min(f[i][j],f[i][k]+2+num[len/l]);
}
}
printf("%d",f[1][n]);
}
int main() {
//freopen("input.txt","r",stdin);
readdata();
work();
return 0;
}