题目大意:
给两个数w和l,分别代表w个单词和一个长度为l的字符串,接下来给出字符串和w个单词,求解最少删掉原字符串中的几个字母可使字符串全部由给出的单词组成,输出最少删掉字母数。
样例输入:
6 10//六个单词,字符串长度为十;
browndcodw//字符串;
cow
milk
white
black
brown
farmer//六个单词如上;
样例输出:
2//最少删掉两个字母;
样例解释:
删掉第六个d和第九个d,原字符串就能由第一和第五个单词组成。
数据范围:
1<=w<=600,2<=l<=300.//相当水的数据;
解题思路:
乍看毫无思路,仔细想想区间dp貌似可以。
简单的说,用长度为numm[j]的第j单词更新以第i字母开头的某一区间时,都可以用第(i+numm[j]+x)字母来更新(暂时不要管x)。
dp[i]表示从第i个字母到末字母的最少删除字母数,倒序循环字母(正序应该也可以),第二重枚举每一个单词,从单词的第一个字母开始与当前的字符串字母匹配,若匹配成功则将两个指针全向右移一位,继续匹配(这里注意加特判,否则会访问无效内存)。直到单词的最后一个字母匹配成功,则用dp[i+numm[j]+x]来更新dp[i](原理很简单,举个实例就明白了),否则用dp[i+1]+1来更新dp[i](证明第i个字母需删掉)。
再解释一下上文的x为何意,如样例的后半部分codw,单词cow与之匹配逐字至字母d时,显然匹配不成功,但直接结束就会出错,原因是当删掉w后,便可以匹配成功。实现的方法是搞一个变量p,若当前匹配失败则将字符串的指针向右移一位,p++,单词指针不变,继续匹配,直到字符串指针指向最后一个仍不能匹配成功时,再按匹配失败更新,注意若匹配成功真正的转移方程是dp[i]=min(dp[i],dp[i+numm[j]+p1]+p1),其中p1为p与单词长度的差。
代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int num,w,l,i,j,k,p=0;
bool q=0;
int main(){
cin>>w>>l;
int dp[l+5];
memset(dp,0x3f,sizeof(dp));
dp[l]=0;
string ll;
cin>>ll;
string ww[w+5];
int numm[w+5];
for(i=0;i<w;i++){
cin>>ww[i];
numm[i]=ww[i].length();
}
for(i=l-1;i>=0;i--)
for(j=0;j<w;j++){
for(k=0;k<numm[j];k++,p++){//这里用p,k两个变量来处理单词指针和p1的问题;
if(i+p>l-1){//特判,不加会RE;
if(dp[i]>1000)//最长300,所以1000即为MAX;
dp[i]=dp[i+1]+1;
q=1;
break;
}
if(ll[i+p]!=ww[j][k]){
if(k==0){//第一个字母如果匹配失败,则一定失败;
if(dp[i]>1000)
dp[i]=dp[i+1]+1;
q=1;
break;
}
if(i+p==l-1){
if(dp[i]>1000)
dp[i]=dp[i+1]+1;
q=1;
break;
}
k--;//处理p1;
}
}
if(!q)
dp[i]=min(dp[i],dp[i+numm[j]+p-k]+p-k);
q=0;
p=0;
}
cout<<dp[0];
return 0;
}