做出这道题关键在于理解语法。其中名词短语和动词短语给出的都是递归定义,可以转化为更直观的,
<名词短语> ::= {<辅词>} <名词>,
<动词短语> ::= {<辅词>} <动词>,
也就是以一个名词或动词结尾,前面可以加上任意多个辅词。而句子就是要以名词短语开头,后面的名词短语和动词短语交替出现。
分析出语法结构,就可以进行动态规划。定义词性
j={0,1,2,3}。
f[i][0][k]表示前i个字母,以i结尾的单词词性为n,构成了k个句子的最小单词数
f[i][1][k]表示前i个字母,以i结尾的单词词性为v,构成了k个句子的最小单词数
f[i][2][k]表示前i个字母,以i结尾的单词词性为a,后面该接v了,构成了k个句子的最小单词数
f[i][3][k]表示前i个字母,以i结尾的单词词性为a,后面该接n了,构成了k个句子的最小单词数
枚举可能以i结尾的单词,设它的词性为type,前一个单词结尾为j。则分类讨论f[i][0/1/2/3][k]可以由f[j][0/1/2/3][k/k-1]的哪些转移而来,具体可以见这个人的分析
找到最小的k,使得min{f[m,0,k],f[m,1,k]}有意义,则最小的句子数为k,单词数为min{f[m,0,k],f[m,1,k]}
时间复杂度为O(nm·maxlen),匹配单词的时候可以用Trie树,第三维状态必须使用滚动数组。
tips:滚动数组每次都要清inf。初值为f[0][0][0]=0
#include
#include
#define N 1005
#define M 6000
#define inf 0x3f3f3f3f
int n,m,maxlen,word[M][22],f[M][4][2],p;//word[i][j]表示以i开头,长度为j的子串是否为单词以及词性
//f[i][0][k]表示前i个字母,以i结尾的单词词性为n,构成了k个句子的最小单词数
//f[i][1][k]表示前i个字母,以i结尾的单词词性为v,构成了k个句子的最小单词数
//f[i][2][k]表示前i个字母,以i结尾的单词词性为a,后面该接v了,构成了k个句子的最小单词数
//f[i][3][k]表示前i个字母,以i结尾的单词词性为a,后面该接n了,构成了k个句子的最小单词数
char s[M];
bool blank=0;
inline int max(int x,int y){return x>y?x:y;}
inline int min(int x,int y){return xstruct Trie{
int val;//记录词性,因为可能有多种词性,用二进制1,10,100累加
Trie *son[26];
Trie(){val=0;memset(son,0,sizeof(son));}
}*root,mempool[N*22],*cnt=mempool;
void insert(Trie *&p,char *s,int type){
if(!p) p=cnt++;
if(!*s){p->val|=type;return;}
insert(p->son[(*s)-'a'],s+1,type);
}
int find(Trie *p,int st,int ed){
for(int i=st;i<=ed;++i){
if(!p->son[s[i]-'a']) return 0;
p=p->son[s[i]-'a'];
}return p->val;
}
int main(){
// freopen("a.in","r",stdin);
while(~scanf("%d",&n)){
if(blank) puts("");
else blank=1;
root=cnt++;maxlen=0;p=0;int ans2=inf,ans1=0;
memset(word,-1,sizeof(word));memset(f,0x3f,sizeof(f));
for(int i=1;i<=n;++i){//建Trie
char s[25];scanf("%s",s);maxlen=max(maxlen,strlen(s));
if(s[0]=='n') insert(root,s+2,1);
else if(s[0]=='v') insert(root,s+2,2);
else insert(root,s+2,4);
}
scanf("%s",s+1);m=strlen(s+1)-1;
f[0][0][0]=0;p=!p;
for(int k=1;k<=m;++k,p=!p){
for(int i=1;i<=m;++i){
f[i][0][p]=f[i][1][p]=f[i][2][p]=f[i][3][p]=inf;//每次都要清inf
for(int j=i-1;j>=i-maxlen&&j>=0;--j){//枚举上一个单词的结尾
if(word[j+1][i-j]==-1) word[j+1][i-j]=find(root,j+1,i);//看是不是单词以及词性
int type=word[j+1][i-j];
if(type&1){//n.
f[i][0][p]=min(f[i][0][p],f[j][1][p]+1);
f[i][0][p]=min(f[i][0][p],f[j][3][p]+1);
f[i][0][p]=min(f[i][0][p],f[j][0][!p]+1);
f[i][0][p]=min(f[i][0][p],f[j][2][!p]+1);
}
if(type&2){//v.
f[i][1][p]=min(f[i][1][p],f[j][0][p]+1);
f[i][1][p]=min(f[i][1][p],f[j][2][p]+1);
}
if(type&4){//a.
f[i][2][p]=min(f[i][2][p],f[j][0][p]+1);
f[i][2][p]=min(f[i][2][p],f[j][2][p]+1);
f[i][3][p]=min(f[i][3][p],f[j][1][p]+1);
f[i][3][p]=min(f[i][3][p],f[j][3][p]+1);
f[i][3][p]=min(f[i][3][p],f[j][0][!p]+1);
f[i][3][p]=min(f[i][3][p],f[j][1][!p]+1);
}
}
}
ans2=min(f[m][0][p],f[m][1][p]);//最小单词数
if(ans2!=inf){ans1=k;break;}//合法了,那此时的k就是最小的句子数
}
printf("%d\n%d\n",ans1,ans2);
}
return 0;
}