说在前面
觉得字符串dp都很神
题目
BZOJ2121传送门
看题可戳传送门
解法
首先me是看了dp定义才会做这道题的
这题….me貌似写不出来思维过程,只能把做法说一说了,可能相关题目做多了之后会有一点感觉吧
考虑dp,如果已知哪些段可以被完全删除,记为
oki,j
o
k
i
,
j
,那么dp是显然的
定义
fi
f
i
表示考虑前
i
i
位的最小答案,则:初值 ,对于每个
okj+1,i=1
o
k
j
+
1
,
i
=
1
,
fi=min( fi , fj )
f
i
=
min
(
f
i
,
f
j
)
那么现在的问题就是如何得出
oki,j
o
k
i
,
j
我们再使用一个dp来完成计算。定义
dp[st][ed][i][j]
d
p
[
s
t
]
[
e
d
]
[
i
]
[
j
]
表示,原字符串
ast...ed
a
s
t
.
.
.
e
d
在删除后,剩下的部分能和第
i
i
个串的前 个字符匹配。使用
dp
d
p
数组和
ok
o
k
数组互相转移
然后这题就做完了
理论复杂度不太对,但是可以压位来降低复杂度emmmm
其实不压也可以过,因为理论上界根本达不到,每一维都空了很多
下面是代码
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std ;
int N , lens[35] , lena , f[155] ;
char ss[35][25] , a[155] ;
bool dp[155][155][35][25] , ok[155][155] ;
void smin( int &x , int y ){ if( x > y ) x = y ; }
void preWork(){
for( int i = 1 ; i <= lena ; i ++ )
for( int j = 1 ; j <= N ; j ++ )
dp[i][i-1][j][0] = true ;
register int k ;
for( int l = 1 ; l <= lena ; l ++ ){
for( int st = 1 , ed = st + l - 1 ; ed <= lena ; st++ , ed++ ){
bool (*t)[35][25] = dp[st] ;
bool &del = ok[st][ed] ;
for( int i = 1 ; i <= N ; i ++ ){
for( int j = 1 ; j <= lens[i] ; j ++ ){
bool &now = t[ed][i][j] ;
if( t[ed-1][i][j-1] && a[ed] == ss[i][j] )
now = true ;
for( k = 1 ; k < l && !now ; k ++ )
if( t[ed-k][i][j] && ok[ed-k+1][ed] ) now = true ;
if( now && !del && j == lens[i] ) del = true ;
}
} if( del ) for( int i = 1 ; i <= N ; i ++ )
t[ed][i][0] = true ;
}
}
}
void solve(){
for( int i = 1 ; i <= lena ; i ++ ){
f[i] = f[i-1] + 1 ;
for( int j = 1 ; j <= i ; j ++ )
if( ok[j][i] ) smin( f[i] , f[j-1] ) ;
} printf( "%d" , f[lena] ) ;
}
int main(){
scanf( "%s%d" , a + 1 , &N ) , lena = strlen( a + 1 ) ;
for( int i = 1 ; i <= N ; i ++ )
scanf( "%s" , ss[i] + 1 ) , lens[i] = strlen( ss[i] + 1 ) ;
preWork() ; solve() ;
}