题目链接
题意
有一个主串$L$,和一个模式串集$S$,每次可以从主串中删除一个模式串,删除后主串剩余的两部分合并,求能得到的主串的最小长度
题解
- 挺神仙的dp
- 如果已经知道了区间$[i,j]$能否被删除的话,那么就可以用$dp[i]$表示对$L$的前$i$个字符进行操作后能得到的最小的长度,转移就很显然。
- 所以难点就在于如何求区间能否被删除。
- 用$f[i][j][k][l]$表示能否将区间$[i,j]$删成第$k$个模式串的前$l$个字符,$ok[i][j]$表示区间$[i,j]$能否全被删掉。从小到大枚举区间长度。
- 转移如下:
- 如果$L[l]=S[k][l]$,则$g[i][j][k][l]=g[i][j-1][k][l-1]$
- 考虑删除的情况:枚举$[i,j]$的分隔点$d$
如果$ok[d+1][j]$,则$g[i][j][k][l]|=g[i][d][k][l]$
- $ok[i][j]|=f[i][j][k][len[k]]$
- 这样处理完后,就可以用最开始说的dp求答案了,时间复杂度$O(|L|^3*k*l)$
查看代码
#include <bits/stdc++.h>
using namespace std;
#define _for(i,a,b) for(int i = (a);i <= (b);++i)
typedef long long ll;
const int maxn = 1e5+5;
const int mod = 1e9+7;
ll qpow(ll a,ll b){ll res = 1;for(;b;b>>=1){if(b&1)res=res*a%mod;a=a*a%mod;}}
struct graph
{
int head[maxn],nxt[maxn<<1],to[maxn<<1],w[maxn<<1],sz;
void init(){memset(head,-1,sizeof(head));}
graph(){init();}
void push(int a,int b,int c){nxt[sz]=head[a],to[sz]=b,w[sz]=c,head[a]=sz++;}
int& operator[](const int a){return to[a];}
};
char s[153];
char t[32][22];
bool f[153][153][32][22];
bool ok[153][153];
int dp[153];
int len[32];
int main()
{
#ifndef ONLINE_JUDGE
freopen("simple.in","r",stdin);
freopen("simple.out","w",stdout);
#endif
int n,m;
scanf("%s%d",s+1,&m);
for(int i = 1;i <= m;++i)scanf("%s",t[i]+1),len[i]=strlen(t[i]+1);
n = strlen(s+1);
for(int i = 1;i <= n;++i){
for(int j = 1;j <= m;++j){
if(s[i]==t[j][1]){
f[i][i][j][1]=1;
if(len[j]==1)ok[i][i]=1;
}
}
}
for(int width = 2;width <= n;++width){
for(int i = 1,j;i <= n;++i){
j = i+width-1;
if(j>n)break;
for(int k = 1;k <= m;++k){
for(int l = 1;l <= len[k];++l){
if(s[j]==t[k][l]){
f[i][j][k][l]|=f[i][j-1][k][l-1];
}
for(int p = i;p < j;++p){
if(ok[p+1][j])f[i][j][k][l]|=f[i][p][k][l];
}
}
ok[i][j]|=f[i][j][k][len[k]];
}
}
}
for(int i = 1;i <= n;++i){
if(ok[1][i])dp[i]=0;
else dp[i]=dp[i-1]+1;
for(int j = 1;j < i;++j){
if(ok[j+1][i])dp[i]=min(dp[i],dp[j]);
}
}
cout<<dp[n]<<endl;
return 0;
}